Why does defining an instance function in terms of another often result in an error?

amx [12:07 PM]
Could someone re-explain to me why defining one typeclass member using another results in a cycle due to strictness, and why it magically works when using do-notation?

garyb [12:13 PM]
if the reference is under a bind then it’s guarded by a function definition, the same way that it would be if eta-expanded (edited)

amx [1:24 PM]
Years of Haskell have made me oblivious to strictness issues… I can read this https://github.com/purescript/purescript/blob/master/src/Language/PureScript/Sugar/TypeClasses.hs as often as I want, I don’t see why it’s a problem to call one instance function from another

natefaubion [5:34 PM]
@amx because in order to call an instance function, you need the dictionary, but you are defining the dictionary in terms of this recursive reference. In a strict language, the binding would be undefined until constructed, thus making the reference invalid. Eta expanding it defers the reference until called.

natefaubion [4 hours ago]
const fooDict = { foo: function() {...}, bar: fooDict.foo }
Is essentially what a strictly evaluated, eta reduced self reference would be. At the time of reference fooDict is undefined since it’s still being constructed.

natefaubion [4 hours ago]
In haskell, fooDict is lazy, so the reference is valid

natefaubion [4 hours ago]
in PureScript, to make this valid, it would need to be
const fooDict = { foo: function() {...}, bar: function(a) { return fooDict.foo(a); } }

natefaubion [4 hours ago]
which defers the reference to fooDict until bar is called, thus making it valid


To aid discovery: The error you may encounter is

  The value of x is undefined here, so this reference is not allowed.