I'm trying to teach myself Purescript properly. I'm currently working through the Purescript book and am on Chapter 6. Since I'm very familiar with Haskell, the exercises have been pretty easy so far. But I'm currently stuck on one, which I certainly know "how" to do but seem to be defeated by Purescript-specific behaviour regarding defining typeclass instances. And which has made me very curious about how this is done in practice.
The specific thing I'm trying to do is to define a Foldable instance for the NonEmpty type, which is defined like this:
data NonEmpty a = NonEmpty a (Array a)
Coming from a Haskell background, I know foldMap tends to be the simplest way to define Foldable instances, so I took no time to write:
instance foldableNonEmpty :: Foldable NonEmpty where
foldMap f (NonEmpty a as) = f a <> foldMap f as
which would be sufficient in Haskell, because there are defaults for all the other Foldable methods in terms of foldMap.
I imagined it would be enough in PureScript too, but my code doesn't compile, giving me the error:
The following type class members have not been implemented:
foldr :: forall a b. (a -> b -> b) -> b -> ... -> b
foldl :: forall a b. (b -> a -> b) -> b -> ... -> b
in type class instance
Data.Foldable.Foldable NonEmpty
This surprised me, seeming inconvenient at best. But I checked the documentation and soon saw that there were indeed predefined ways to get foldl and foldr from foldMap, in the form of foldlDefault and foldrDefault. So my next attempt was:
instance foldableNonEmpty :: Foldable NonEmpty where
foldMap f (NonEmpty a as) = f a <> foldMap f as
foldr = foldrDefault
foldl = foldlDefault
But this, too, fails to compile. The error this time is:
The value of foldableNonEmpty is undefined here, so this reference is not allowed.
which is a little mysterious to me, but I think it means that I can't yet access foldrDefault and foldlDefault, because (according to the Pursuit documentation) they require the type constructor to already have a Foldable instance, so can't be used as part of defining the instance.
But all this of course begs the question: how can I define a Foldable instance in Purescript without having to manually write out redundant definitions for 2 of the 3 methods? Similarly with other typeclasses that define methods in terms of each other. Or if it is possible (I hope so!), what am I missing?
Actually, after searching Google a bit harder, I did find an example of a Foldable instance, and found that the instance does compile if I do this:
instance foldableNonEmpty :: Foldable NonEmpty where
foldMap f (NonEmpty a as) = f a <> foldMap f as
foldr f = foldrDefault f
foldl f = foldlDefault f
But this begs a new question: since PureScript, as far as I understand it, uses currying in exactly the same way as Haskell, why is this allowed while the "eta-reduced" version above is not? And what does the error message about a value being undefined have to do with anything?