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?