BSD-3-CLAUSE License
Based on this blog post. A
Kleisli functor of m
is a functor from Kliesli m
to Hask
.
class (Monad m, Functor f) =>
KleisliFunctor m f where
kmap :: (a -> m b) -> f a -> f b
These are functors that are able to absorb the monad. For instance,
lists are a Kleisli functor of Maybe
, because the list functor can
absorb the Maybe
monad by removing Nothing
elements.
instance KleisliFunctor Maybe [] where
kmap _ [] = []
kmap f (a:as) =
case f a of
-- Remove `Nothing` elements
Nothing -> kmap f as
-- Keep `Just` elements
Just b -> b:kmap f as
There are many other ways to use Kleisli functors, concurrency being
my favorite example. Using the async
package, we get access to a
Concurrently
type for doing IO
concurrently. Along with
Traversable
, this makes for a really useful Kleisli functor.
kfor
:: (Traversable t, KleisliFunctor m f, Applicative f)
=> (a -> m b) -> t a -> f (t b)
kfor t f = for t (kmap f . pure)
instance KleisliFunctor IO Concurrently where
kmap f (Concurrently a) = Concurrently (a >>= f)
run
:: (Traversable t, KleisliFunctor IO f, Applicative f)
=> t Int -> f (t Int)
run t = kfor t $ \i -> do
-- This block lives in 'IO',
-- and will be executed according to f's applicative instance.
...
return x
concurrent
:: Traversable t
=> t Int -> Concurrently (t Int)
concurrent = run
sequential
:: Traversable t
=> t Int -> IO (t Int)
sequential = run
Here, run
is defined in terms of some concurrency strategy f
. By
plugging in IO
, you get sequential operations. By plugging in
Concurrently
, it becomes concurrent automatically. Kleisli functors
allow run
to be defined so it doesn't care about the details of how
it will be executed.