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



If

do
x <- y
f x

相当于:

y >>= x -> f x

然后

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

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

如果我做对了,我/认为/在一个类似做的前奏会话中,我可以像这样翻译:

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

自然是失败的。

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

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 并不是一个真正的"正常"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;博士

注意类型,并记住GHCi有很多方便的魔力。

最新更新