Haskell在简单文件字段计数中重构do块以使用(>> =)绑定

时间:2017-01-15 02:55:17

标签: haskell

如果

do
    x <- y
    f x

相当于:

y >>= \x -> f x

然后

为什么会这样(“检索文件最后一行的逗号分隔字段数”):

λ> lline <- (last . lines) <$> readFile fname
λ> length $ split "," lline
11

如果我这样做的话,我/想/在类似Prelude的会话中,我可以这样翻译:

x := lline
y := (last . lines) <$> readFile fname
f := (\lline -> length $ split "," lline)

不等于

λ> ((last . lines) <$> readFile fname) >>= (\lline -> (length $ split "," lline))

<interactive>:97:53-76: error:
    • Couldn't match expected type ‘IO b’ with actual type ‘Int’
    • In the expression: (length $ split "," lline)

或者,我试图进行“无点”翻译

λ> ((last . lines) <$> readFile fname) >>= (length $ split ",")

<interactive>:154:42-59: error:
    • Couldn't match expected type ‘String -> IO b’
                  with actual type ‘Int’

我想将Int分配给一个值。也许我在错误的地方括号或者误译了?

一方面,我看到这“有效”:

λ> ((last . lines) <$> readFile fname) >>= (\x -> putStrLn $ show $ length $ split "," x)
11

所以我知道我很接近。这对于打印很好,但是如何将Int分配到这个翻译和去掉的形式的某个地方?

即使我的直觉,怪异的新手想法试图用read读取该字符串:

λ> val = read $ ((last . lines) <$> readFile fname) >>= (\x -> length $ split "," x) :: Int

自然会失败。

1 个答案:

答案 0 :(得分:3)

正如你所说,以下两个是等价的:

do x <- y
   f x
y >>= (\x -> f x)

但是,为了实际工作,我们必须尊重(>>=)的类型:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

如果我们看一下你的表达方式,我们会发现某些事情并不完全正确:

((last . lines) <$> readFile fname)    :: IO String     -- seems right
(\lline -> (length $ split "," lline)) :: String -> Int -- no IO on the right hand side!

您的功能length . split ","不会在IO中返回内容,而是返回Int。因此,您必须使用print或类似内容来展示它,或者返回IO Int(取决于您想要做的事情):

((last . lines) <$> readLine fname) >>= print . length . split ","

请记住,GHCi并非真正的&#34;正常&#34; do阻止,否则简单的表达式,如

ghci> 1 + 1
2

会导致类型错误。相反,GHCi会检查类型是否为IO a。如果是,则运行操作,如果aShow的实例,则还会显示结果:

ghci> data Foo = Foo -- no instance of Show
ghci> return Foo -- no output!
ghci> return (1 + 1)
2

如果您有另一个没有x类型的IO a表达式,它将充当print x,包括Show个实例错误:

ghci> Foo 

No instance for (Show Foo) arising from a use of `print'
ghci> 1 + 1
2

TL; DR

注意类型并记住GHCi为方便起见有很多魔力。