How to use Kleisli arrows with monads?

In the Haskell Control.Arrow documentation, he talks about the relationship of Kleisli arrows with monads, but for me it is not obvious how to use this. I have a function that I think is suitable for shooters other than it with the participation of the IO monad, so I think Claysley arrows can help.

Take the following function, which returns a pair of source and changed directory file names.

 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 do this, it would be something like this:

enter image description here

I think this can benefit from using the Kleisli arrows, but I don't know how to do this. Can anyone give directions?

+7
haskell monads arrows kleisli
source share
4 answers

datedFiles can be implemented using arrows, because the information goes into a "fixed pipeline", as your diagram shows.

Here's a possible implementation that doesn't use map or zip in lists:

 import System.Directory import System.FilePath import Control.Monad.List import Control.Arrow datedFiles :: FilePath -> IO [(FilePath,FilePath)] datedFiles = fmap runListT . runKleisli $ (Kleisli $ ListT . getDirectoryContents) >>> returnA &&& ((Kleisli $ liftIO . getModificationTime) >>^ show) >>^ fst &&& (\(path,time) -> replaceBaseName path $ takeBaseName path ++ time) 

This may not be the most intuitive implementation.

The monad for Claysley arrows is ListT IO , although the only non-determinism is called getDirectoryContents .

Note that the last line is a pure function; (&&&) for the last line uses an Arrow instance for functions.

Edit: The Wrapped typeclass from the lens package can be used to add / remove newtype wrappers a little more succinctly. Applying it to the previous example, we get:

 import Control.Lens datedFiles :: FilePath -> IO [(FilePath,FilePath)] datedFiles = fmap runListT . runKleisli $ ListT . getDirectoryContents ^. wrapped >>> returnA &&& (liftIO . getModificationTime ^. wrapped >>^ show) >>^ fst &&& (\(path,time) -> replaceBaseName path $ takeBaseName path ++ time) 
+6
source share

Functor monads from Hask, the Haskell type and function category, to Hask --- endofunctor. This means that some of the arrows in Hask look like a -> mb for some Monad m . For a particular monad m Hask subcategory, where the arrows look like a -> mb , is the Claysley category for m .

We know that this is a category because the identity exists arrow return :: a -> ma and composition (>>>) :: (a -> mb) -> (b -> mc) -> (a -> mc) defined as

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

so we need it to be Monad --- we use both return and join .


In Haskell, we cannot just have a subcategory in normal mode, but instead use a new type.

 import Prelude hiding ((.), id) import Control.Category newtype Kleisli mab = Kleisli { runKleisli :: a -> mb } instance Monad m => Category (Kleisli m) where id = Kleisli return Kleisli g . Kleisli f = Kleisli (join . fmap g . f) 

And then we can update functions like Monad m => a -> mb to Kleisli mab s, arrows in the category and put them together with (.)

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

This is usually a little syntactically noisy. The new type is only useful for using the Category class to overload id and (.) . Instead, it is more likely that you will see return and (>=>) which are equivalent

 return a = runKleisli (id a) f >=> g = runKleisli $ Kleisli g . Kleisli f 
+7
source share

First, I suggest you separate the processing of a single file from the processing of a list. In your example, timestamp is an interesting arrow, because all the others are pure functions. However, we can make some of them arrows to make the example more interesting. Using the arrow notation , we can rewrite the calculation of a single file name as Kleisli arrows:

 {-# LANGUAGE Arrows #-} import Control.Arrow import System.Directory import System.FilePath import System.Time -- Get a timestamp of a file as an arrow: timestamp :: Kleisli IO FilePath ClockTime timestamp = Kleisli getModificationTime -- Insert a given string in front of the extension of a file. -- Just as an example - we'd rather use a simple `let` instead of making it -- an arrow. append :: (Monad m) => Kleisli m (FilePath, String) FilePath append = arr $ \(fn, suffix) -> let (base, ext) = splitExtension fn in base ++ suffix ++ ext -- Given a directory, receive the name of a file as an arrow input -- and produce the new file name. (We could also receive `dir` -- as an input, if we wanted.) datedArrow :: FilePath -> Kleisli IO FilePath (FilePath, FilePath) datedArrow dir = proc fn -> do ts <- timestamp -< replaceDirectory fn dir fn' <- append -< (fn, show ts) returnA -< (fn, fn') datedFiles' :: FilePath -> IO [(FilePath, FilePath)] datedFiles' target = do fns <- getDirectoryContents target mapM (runKleisli $ datedArrow target) fns 
+2
source share

Let me remember the main function from Monad:

 (>>=) :: (a -> mb) -> ma -> mb 

and now look at Kleisli

 newtype Kleisli mab = Kleisli { runKleisli :: a -> mb } 

where Kleisli is a wrapper and runKleisli is a sweep of a new type.

What common? a -> mb part

And look at the instance declaration:

 instance Monad m => Arrow (Kleisli m) where ... 

we see how to make a Monad part of an Arrow

+1
source share

All Articles