如何使用带有monads的Kleisli箭头?

Opa*_*Opa 7 monads haskell arrows kleisli

在Haskell Control.Arrow文档中,它讨论了Kleisli箭头与monad的关系,但对我来说如何使用它并不明显.我有一个我认为适合箭头的功能,除了它涉及IO monad,所以我认为Kleisli箭头可能有帮助.

使用以下函数返回目录的原始和修改文件名对.

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
Run Code Online (Sandbox Code Playgroud)

如果我必须把它画出来,它将是这样的:

在此输入图像描述

我认为它可以从Kleisli箭头的使用中受益,但我不知道如何.有人可以提供指导吗?

J. *_*son 8

Monads是Functor从Hask(Haskell类型和函数的类别)到Hask ---一个endofunctor.这意味着Hask中的一些箭头看起来像是a -> m b一些Monad m.对于特定的monad m,箭头看起来像Hask的子类别a -> m b是Kleisli类别m.

我们知道这是一个类别,因为有一个箭头标识return :: a -> m a和成分(>>>) :: (a -> m b) -> (b -> m c) -> (a -> m c)等被定义

(f >>> g) a = join (g <$> f a)
Run Code Online (Sandbox Code Playgroud)

这就是为什么我们需要这个Monad- 我们使用returnjoin.


在Haskell中,我们通常不能只有一个子类别,而是使用newtype.

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)
Run Code Online (Sandbox Code Playgroud)

然后我们可以将类型的函数升级Monad m => a -> m bKleisli m a bs,类别中的箭头,并用它们组合(.)

arr :: Kleisli IO FilePath [String]
arr = Kleisli (mapM $ fmap show . getModificationTime) . Kleisli getDirectoryContents
Run Code Online (Sandbox Code Playgroud)

但一般来说,这有点语法噪音.该NEWTYPE仅仅是为了使用有价值的Category类型类超载id(.).相反,你更有可能看到return,(>=>)哪些等同于

return a = runKleisli (id a)
f >=> g  = runKleisli $ Kleisli g . Kleisli f
Run Code Online (Sandbox Code Playgroud)


dan*_*iaz 6

datedFiles 可以使用箭头实现,因为信息在"固定管道"中流动,如图所示.

这是一个不使用mapzip在列表上的可能实现:

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)
Run Code Online (Sandbox Code Playgroud)

可以说,它不是最直观的实现.

Kleisli箭的monad是ListT IO,虽然唯一的非确定性是由getDirectoryContents.

注意最后一行是纯函数; 在(&&&)最后一行用箭头实例功能.

编辑:包裹从类型类lens包可用于增加/删除NEWTYPE包装有点更简洁.将它应用于前面的示例,我们最终得到:

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)
Run Code Online (Sandbox Code Playgroud)