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箭头的使用中受益,但我不知道如何.有人可以提供指导吗?
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- 我们使用return和join.
在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 b为Kleisli 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)
datedFiles 可以使用箭头实现,因为信息在"固定管道"中流动,如图所示.
这是一个不使用map或zip在列表上的可能实现:
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)