Problem:
Recently I asked the following question on here, asking how to create a generic map function, and a generic instance of Functor
for any arbitrary polymorphic ADT (Algebraic Data Type), like Lists, Trees, etc:
Functor instance for generic polymorphic ADTs in Haskell?
Now, I'm trying to reformulate the above to be compatible with recursion-schemes
. I.e, instead of defining the base functor, and then define the type as its fixed point, I want to define the type on one hand, the base functor on the other, and correlate them using the Base
family type.
So instead of doing this:
data ListF a b = NilF | ConsF a b
newtype Fix f = Fix { unFix :: f (Fix f) }
type List a = Fix (ListF a)
I want to do this:
data ListF a b = NilF | ConsF a b
data List a = Nil | Cons a (List a)
type instance Base (List a) = ListF a
This way I could leverage the power of the recursion-schemes
library, while still being able to define a generic fmap
for any of these polymorphic types. Not only that, but it's a more pleasant experience being able to use the "normal" type without it being a type synonym for the fix point.
Attempt:
Initially I thought about having a Bifunctor
instance on one hand, and somehow coercing or making it equal to the corresponding Base
family instance. At the moment I can only think about using a :~: b
from Data.Type.Equality
. This is what I've got so far:
{-# LANGUAGE TypeOperators, Rank2Types #-}
import Data.Bifunctor
import Data.Functor.Foldable
import Data.Type.Equality
gmap :: (Bifunctor p, Foldable (f a), Unfoldable (f b)) =>
(forall x. p x :~: Base (f x)) -> (a -> b) -> f a -> f b
gmap refl f = cata alg
where
alg = embed .
castWith (apply refl Refl) .
bimap f id .
castWith (apply (sym refl) Refl)
My problem comes in trying to define an instance of Functor
though. I don't know how to specify those specific type constraints when defining the instance.
I was thinking about somehow creating a typeclass Equals
, and doing something like this:
instance (Bifunctor p, Foldable (f a), Unfoldable (f b), Equals (p a) (Base (f a)))
=> Functor f where
But I don't know if that's possible, nor if I'm approaching it in the right way (for instance I'm not sure if my definition of gmap
is the correct one).
For reference, this is the definition of the generic gmap
from the original SO question:
gmap :: (Bifunctor f) => (a -> b) -> Fix (f a) -> Fix (f b)
gmap f = unwrapFixBifunctor . cata alg . wrapFixBifunctor
where
alg = Fix . bimap f id
unwrapFixBifunctor :: (Bifunctor f) => Fix (WrappedBifunctor f a) -> Fix (f a)
unwrapFixBifunctor = Fix . unwrapBifunctor . fmap unwrapFixBifunctor . unFix
wrapFixBifunctor :: (Bifunctor f) => Fix (f a) -> Fix (WrappedBifunctor f a)
wrapFixBifunctor = Fix . fmap wrapFixBifunctor . WrapBifunctor . unFix
Update:
It was noted that the following definition of gmap
would be more general, and would not need any weird application of type-level equality:
gmap :: (Foldable t, Unfoldable d, Bifunctor p, Base d ~ p b, Base t ~ p a)
=> (a -> b) -> t -> d
gmap f = cata ( embed . bimap f id )
However, I still can't find a way to create an instance of Functor
that has similar type constraints
:~:
? Your function is probably impractical if you have to call it withRefl
all the time. 2. If I writegmap f = cata ( embed . bimap f id )
and let the compiler infer the type, I get(Foldable t, Unfoldable d, Bifunctor p, Base d ~ p b, Base t ~ p a) => (a -> b) -> t -> d
. Is there anything wrong with this most general type? You can have a more specific type if you like. The compiler is perfectly happy with your function, so I don't know what the actual question is. – user2407038Functor
usingfmap = gmap
– gonzaw