IO monad中的功能组成

Ger*_*ely 1 io monads haskell function-composition io-monad

linesHaskell中的函数将字符串的行分成字符串列表:

lines :: String -> [String]
Run Code Online (Sandbox Code Playgroud)

readFile函数将文件读取为字符串:

readFile :: FilePath -> IO String
Run Code Online (Sandbox Code Playgroud)

尝试组合这些函数以获取文件中的行列表会导致类型错误:

Prelude> (lines . readFile) "quux.txt"
<interactive>:26:10: error:
    • Couldn't match type ‘IO String’ with ‘[Char]’
      Expected type: FilePath -> String
        Actual type: FilePath -> IO String
    • In the second argument of ‘(.)’, namely ‘readFile’
      In the expression: lines . readFile
      In the expression: (lines . readFile) "quux.txt"
Run Code Online (Sandbox Code Playgroud)

我该怎么做单子戏?

che*_*ner 6

您无法编写它们,至少不能(.)单独使用它们。不过,您可以使用fmap(或其操作符版本<$>):

lines <$> readFile "quux.txt"  -- Produces IO [String], not [String]
Run Code Online (Sandbox Code Playgroud)

用一种合成表达这种方式的一种方法是首先从以下项中创建一个Kleisli箭头(a -> m b某些monad 的类型函数mlines

-- return . lines itself has type Monad m => String -> m [String]
-- but for our use case we can restrict the type to the monad
-- we are actually interested in.
kleisliLines :: String -> IO [String]
kleisliLines = return . lines
Run Code Online (Sandbox Code Playgroud)

现在,您可以使用Kleisli合成运算符>=>组合readFile(本身就是Kleisli箭头)和lines

import Control.Monad  -- where (>=>) is defined

-- (>=>) :: Monad m => (a -> m b) -> (b -> m c) -> a -> m c
-- Here, m ~ IO
--       a -> FilePath
--       b -> String
--       c -> [String]
(readFile >=> kleisliLines) "quux.txt"
Run Code Online (Sandbox Code Playgroud)

将此与>>=操作员进行比较,该操作员需要在提供readFile结果之前将文件名提供给return . lines

-- m >>= return . f === fmap f m === f <$> m
readFile "quux.txt" >>= kleisliLines
Run Code Online (Sandbox Code Playgroud)

>=>如果您已经在考虑管道的话,这是很自然的>=;如果您想要某种保留,顺序的东西.,请使用<=<(也在中定义Control.Monad(<=<) = flip (>=>),操作数只是颠倒过来)。

(kleisliLines <=< readFile) "quux.txt"
Run Code Online (Sandbox Code Playgroud)