繁体   English   中英

Haskell 如何在这个 do 块中“脱糖”?

[英]How does Haskell "desugar" getline in this do block?

我读过几本关于 Haskell 的书,但没有编写太多代码,而且我对 Haskell 在某种情况下的作用有点困惑。 假设我正在使用getLine ,这样用户可以按一个键继续,但我真的不想以任何有意义的方式解释那个人的输入。 我相信这是这样做的有效方法:

main = do
    _ <- getLine
    putStrLn "foo"

我了解这是在做什么的基本要点。 getLine返回一个IO String ,而putStrLn接受一个String并返回IO () ,所以如果我理论上想打印用户在控制台中输入的内容,我基本上会使用来自Monad class 的>>=运算符。在我的例子中,我相信我的代码等同于getLine >> putStrLn "foo"因为我丢弃了getLine的返回值。

但是,如果我这样做呢?

main = do
    let _ = getLine
    putStrLn "foo"

在这种情况下,我们正在设置一种 lambda 来处理需要IO String的东西,对吗? 我可以写一个printIOString function 来打印用户的输入,这样就可以了。 但是,当我实际上没有使用IO String时,程序的行为很奇怪...... getLine甚至不提示我输入; 该程序只是打印出"foo"

我不太确定这里的“脱糖”语法是什么,或者这是否会揭示 Haskell 在幕后所做的事情。

让我们用几个更复杂的例子来热身。

main = do
    x
    x
    x
    putStrLn "foo"
    where
    x = do
        getLine

你希望它做什么? 我不了解你,但期望程序获得三行然后打印一些东西。 如果我们对第二个do块进行脱糖处理,我们会得到

main = do
    x
    x
    x
    putStrLn "foo"
    where x = getLine

因为这是另一个的脱糖,所以它的行为相同,在打印前得到三行。 如果您觉得第一个不直观,还有另一种思路可以得出相同的答案。 “引用透明”是Haskell的定义特性之一,准确的意思是你可以用它的定义替换对某个东西(即变量名)的“引用”,所以前面的程序应该和下面的程序完全一样

main = do
    getLine
    getLine
    getLine
    putStrLn "foo"

如果我们认真对待方程x = getLine 好的,所以我们有一个读取三行并打印的程序。 这个如何?

main = do
    x
    x
    putStrLn "foo"
    where x = getLine

获取两行并打印。 还有这个?

main = do
    x
    putStrLn "foo"
    where x = getLine

得到一行然后打印。 希望你看到这是怎么回事......

main = do
    putStrLn "foo"
    where x = getLine

获取零行然后打印,即立即打印! 我使用where而不是let来使开头的示例更加明显,但是您几乎总是可以用它的let表亲替换where块而不改变其含义:

main = let x = getLine in do
    putStrLn "foo"

由于我们不引用x ,我们甚至不需要命名它:

main = let _ = getLine in do
    putStrLn "foo"

这是您编写的代码的脱糖。

第一种情况像您预期的那样脱糖:

main = getLine >>= \_ -> putStrLn "foo"

这相当于

main = getLine >> putStrLn "foo"

在第二种情况下,

main = do
    let _ = getLine
    putStrLn "foo"

被脱糖为

main = let _ = getLine in putStrLn "foo"

由于不需要_ = getLine值来评估let表达式的 RHS,因此编译器可以随意忽略它并且永远不会执行 IO 效果,这就是为什么不再提示您进行 CLI 输入的原因。

尽管这两种情况都忽略了getLine的结果,但区别在于第一种情况在IO上下文中评估getLine ,而第二种情况将getLine评估为纯值。 IO中,副作用必须一起执行和排序,但在纯上下文中,编译器可以自由地忽略未使用的值。

我不建议这样做,因为它不是很惯用,但你可以写类似的东西

printIOString :: IO String -> IO ()
printIOString ios = ios >>= putStrLn

并像printIOString getLine一样使用它

根据https://stackoverflow.com/tags/do-notation/info

do { let { _ = getLine } ; putStrLn "foo" } 
= 
do { let { _ = getLine } in putStrLn "foo" } 
= 
     let { _ = getLine } in putStrLn "foo"

Haskell 语义等同于

    getLine & (\ _ -> putStrLn "foo")
=
                      putStrLn "foo"

(与x & f = fx ),而确实

do { _ <- getLine ; putStrLn "foo" }
=
  getLine >>= (\ _ -> putStrLn "foo")

无法进一步简化。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM