kleisli-functors

BSD-3-CLAUSE License

Stars
7

kleisli-functors

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.