FFunctor: functors on (the usual) Functors.
FFunctor is a type class for endofunctors on the category of Functors.
FMonad is a type class for monads in the category of Functors.
functor-monad: Monads on category of Functors
This package provides FFunctor and FMonad, each corresponds to Functor and Monad but higher-order.
a Functor f | a FFunctor ff | |
|---|---|---|
| Takes | a :: Type | g :: Type -> Type, Functor g |
| Makes | f a :: Type | ff g :: Type -> Type, Functor (ff g) |
| Methods | fmap :: (a -> b) -> f a -> f b | ffmap :: (Functor g, Functor h) => (g ~> h) -> (ff g ~> ff h) |
a Monad m | a FMonad mm | |
|---|---|---|
| Superclass | Functor | FFunctor |
| Methods | return = pure :: a -> m a | fpure :: (Functor g) => g ~> mm g |
(=<<) :: (a -> m b) -> m a -> m b | fbind :: (Functor g, Functor h) => (g ~> mm h) -> (mm g ~> mm h) | |
join :: m (m a) -> m a | fjoin :: (Functor g) => mm (mm g) ~> mm g |
See also: https://viercc.github.io/blog/posts/2020-08-23-fmonad.html (Japanese article)
Motivational examples
Many types defined in base and popolar libraries like transformers take a parameter expecting a Functor. Here are two, simple examples.
-- From "base", Data.Functor.Sum
data Sum f g x = InL (f x) | InR (g x)
instance (Functor f, Functor g) => Functor (Sum f g)
-- From "transformers", Control.Monad.Trans.Reader
newtype ReaderT r m x = ReaderT { runReaderT :: r -> m x }
instance (Functor m) => Functor (ReaderT r m)
These types often have a way to map a natural transformation, an arrow between two Functors, over that parameter.
-- The type of natural transformations
type (~>) f g = forall x. f x -> g x
mapRight :: (g ~> g') -> Sum f g ~> Sum f g'
mapRight _ (InL fx) = InL fx
mapRight nt (InR gx) = InR (nt gx)
mapReaderT :: (m a -> n b) -> ReaderT r m a -> ReaderT r n b
-- mapReaderT can be used to map natural transformation
mapReaderT' :: (m ~> n) -> (ReaderT r m ~> ReaderT r n)
mapReaderT' naturalTrans = mapReaderT naturalTrans
The type class FFunctor abstracts type constructors equipped with such a function.
class (forall g. Functor g => Functor (ff g)) => FFunctor ff where
ffmap :: (Functor g, Functor h) => (g ~> h) -> ff g x -> ff h x
ffmap :: (Functor g, Functor g') => (g ~> g') -> Sum f g x -> Sum f g' x
ffmap :: (Functor m, Functor n) => (m ~> n) -> ReaderT r m x -> ReaderT r n x
Not all but many FFunctor instances can, in addition to ffmap, support Monad-like operations. This package provide another type class FMonad to represent such operations.
class (FFunctor mm) => FMonad mm where
fpure :: (Functor g) => g ~> mm g
fbind :: (Functor g, Functor h) => (g ~> ff h) -> ff g a -> ff h a
Both of the above examples, Sum and ReaderT r, have FMonad instances.
instance Functor f => FMonad (Sum f) where
fpure :: (Functor g) => g ~> Sum f g
fpure = InR
fbind :: (Functor g, Functor h) => (g ~> Sum f h) -> Sum f g a -> Sum f h a
fbind _ (InL fa) = InL fa
fbind k (InR ga) = k ga
instance FMonad (ReaderT r) where
fpure :: (Functor m) => m ~> ReaderT r m
-- return :: x -> (e -> x)
fpure = ReaderT . return
fbind :: (Functor m, Functor n) => (m ~> ReaderT r n) -> ReaderT r m a -> ReaderT r n a
-- join :: (e -> e -> x) -> (e -> x)
fbind k = ReaderT . (>>= runReaderT . k) . runReaderT
Comparison against similar type classes
There are packages with very similar type classes, but they are more or less different to this package.
From hkd:
FFunctorThere is a class named
FFunctorinhkdpackage too. It represents a functor from /category of type constructors/k -> Typeto the category of usual types and functions.Since it is not an endofunctor, there can be no
Monad-like classes on them.Another package rank2classes also provides the same class
Rank2.Functor.From mmorph:
MFunctor,MMonadMFunctoris a class for endofunctors on the category ofMonads and monad homomorphisms. IfTis aMFunctor, it takes aMonad mand constructsMonad (T m), and itshoistmethod takes a Monad morphismm ~> nand returns a new Monad morphismT m ~> T n.On the other hand, this library is about endofunctors on the category of
Functors and natural transformations, which are similar but definitely distinct concept.For example,
Sum fin the example above is not an instance ofMFunctor, since there are no general way to makeSum f maMonadfor arbitraryMonad m.instance Functor f => FFunctor (Sum f) instance Functor f => MFunctor (Sum f) -- Can be written, but it will violate the requirement to be MFunctorFrom index-core:
IFunctor,IMonadThey are endofunctors on the category of type constructors of kind
k -> Typeand polymorphic functionst :: forall (x :: k). f x -> g x.While any instance of
FFunctorfrom this package can be faithfully represented as aIFunctor, some instances can't be an instance ofIFunctoras is. Most notably, Free can't be an instance ofIFunctordirectly, becauseFreeneedsFunctor hto be able to implementfmapI, the method ofIFunctor.class IFunctor ff where fmapI :: (g ~> h) -> (ff g ~> ff h)There exists a workaround: you can use another representation of
Free fwhich doesn't requireFunctor fto be aFunctoritself, for exampleProgramfrom operational package.This package avoids the neccesity of the workaround by admitting the restriction that the parameter of
FFunctormust always be aFunctor. Therefore,FFunctorgives up instances which don't takeFunctorparameter, for example, a type constructorFwith kindF :: (Nat -> Type) -> Nat -> Type.From functor-combinators:
HFunctor,Inject,HBindThis package can be thought of as a more developed version of
index-core, since they share the base assumption. The tradeoff between this package is the same: someFFunctorinstances can only beHFunctorvia alternative representations. Same applies forFMonadversusInject + HBind.