IO monad中的功能组成

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

lines :: String -> [String]

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

readFile :: FilePath -> IO String

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

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"

我该怎么做单子戏?

xiaomeng722 回答:IO monad中的功能组成

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

lines <$> readFile "quux.txt"  -- Produces IO [String],not [String]

用一种合成表达这种方式的一种方法是,首先从a -> m b创建一个Kleisli箭头(某单子m的类型为lines的函数):>

-- 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

现在,您可以使用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"

将其与>>=运算符进行比较,该运算符要求您在将结果提供给readFile之前将文件名提供给return . lines

-- m >>= return . f === fmap f m === f <$> m
readFile "quux.txt" >>= kleisliLines

>=>很自然,如果您已经想到了>=方面的管道;如果您想要某种保留.顺序的东西,请使用<=<(也在Control.Monad中定义为(<=<) = flip (>=>);操作数将被颠倒)。

(kleisliLines <=< readFile) "quux.txt"
,

到目前为止,给出的其他答案是使lines生成一个空的单子上下文,然后使用单子组合(<=<)与readFile进行组合。但是您也可以朝另一个方向前进:提起lines以通过一元论证进行运算,然后使用普通的合成将其与readFile结合起来:

(fmap lines . readFile) "quux.txt"

当然,如果您要立即将其应用于参数,则只需编写即可

lines <$> readFile "quux.txt"
本文链接:https://www.f2er.com/3144033.html

大家都在问