Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Kleisli arrows with monads?

In the Haskell Control.Arrow documentation it talks about Kleisli arrows' relationship to monads, but it is not obvious to me how to use this. I have a function which I think fits with arrows except for it involving the IO monad, so I think Kleisli arrows may help.

Take the following function which returns pairs of original and modified filenames of a directory.

import System.Directory
import System.FilePath

datedFiles target = do
    fns <- getDirectoryContents target
    tms <- mapM (fmap show . getModificationTime) fns
    return $ 
        zip fns $ 
        zipWith replaceBaseName fns $ 
        zipWith (++) (map takeBaseName fns) tms

If I had to draw it out, it would be something like this:

enter image description here

I think it can benefit from the use of Kleisli arrows, but I don't know how. Can anyone provide guidance?

like image 325
Opa Avatar asked Nov 23 '13 17:11

Opa


1 Answers

Monads are Functors from Hask, the category of Haskell types and functions, to Hask---an endofunctor. That means that some of the arrows in Hask look like a -> m b for some Monad m. For a particular monad m, the subcategory of Hask where arrows look like a -> m b is the Kleisli category for m.

We know it's a category because there's an identity arrow return :: a -> m a and composition (>>>) :: (a -> m b) -> (b -> m c) -> (a -> m c) defined like

(f >>> g) a = join (g <$> f a)

which is why we need this to be a Monad---we use both return and join.


In Haskell, we can't just have a subcategory normally, but instead a newtype is used.

import Prelude hiding ((.), id)
import Control.Category

newtype Kleisli m a b = Kleisli { runKleisli :: a -> m b }

instance Monad m => Category (Kleisli m) where
  id                    = Kleisli return
  Kleisli g . Kleisli f = Kleisli (join . fmap g . f)

And then we can upgrade functions of type Monad m => a -> m b to Kleisli m a bs, arrows in a category, and compose them with (.)

arr :: Kleisli IO FilePath [String]
arr = Kleisli (mapM $ fmap show . getModificationTime) . Kleisli getDirectoryContents

Generally that's a bit syntactically noisy, though. The newtype is only valuable in order to use the Category typeclass to overload id and (.). Instead it's more likely that you'll see return and (>=>) which are equivalent to

return a = runKleisli (id a)
f >=> g  = runKleisli $ Kleisli g . Kleisli f
like image 86
J. Abrahamson Avatar answered Oct 21 '22 14:10

J. Abrahamson