简体   繁体   English

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

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

I've read a few books on Haskell but haven't coded in it all that much, and I'm a little confused as to what Haskell is doing in a certain case.我读过几本关于 Haskell 的书,但没有编写太多代码,而且我对 Haskell 在某种情况下的作用有点困惑。 Let's say I'm using getLine so the user can push a key to continue, but I don't really want to interpret that person's input in any meaningful way.假设我正在使用getLine ,这样用户可以按一个键继续,但我真的不想以任何有意义的方式解释那个人的输入。 I believe this is a valid way of doing this:我相信这是这样做的有效方法:

main = do
    _ <- getLine
    putStrLn "foo"

I understand the basic gist of what's this is doing.我了解这是在做什么的基本要点。 getLine returns an IO String , and putStrLn takes a String and returns IO () , so if I theoretically wanted to print what the user typed into the console, I'd basically utilize the >>= operator from the Monad class. In my case, I believe my code is equivalent to getLine >> putStrLn "foo" since I'm discarding the return value of getLine . getLine返回一个IO String ,而putStrLn接受一个String并返回IO () ,所以如果我理论上想打印用户在控制台中输入的内容,我基本上会使用来自Monad class 的>>=运算符。在我的例子中,我相信我的代码等同于getLine >> putStrLn "foo"因为我丢弃了getLine的返回值。

However, what if I do this instead?但是,如果我这样做呢?

main = do
    let _ = getLine
    putStrLn "foo"

In this case, we're setting up a sort of lambda to work with something that will take an IO String , right?在这种情况下,我们正在设置一种 lambda 来处理需要IO String的东西,对吗? I could write a printIOString function to print the user's input and that would work fine.我可以写一个printIOString function 来打印用户的输入,这样就可以了。 When I'm not actually using that IO String , though, the program behaves strangely... getLine doesn't even prompt me for input;但是,当我实际上没有使用IO String时,程序的行为很奇怪...... getLine甚至不提示我输入; the program just prints out "foo" .该程序只是打印出"foo"

I'm not really sure what the "desugared" syntax would be here, or if that would shed some light on what Haskell is doing under the hood.我不太确定这里的“脱糖”语法是什么,或者这是否会揭示 Haskell 在幕后所做的事情。

Let's warm up with a few more complicated examples.让我们用几个更复杂的例子来热身。

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

What do you expect this to do?你希望它做什么? I don't know about you, but what I expect is for the program to get three lines and then print something.我不了解你,但期望程序获得三行然后打印一些东西。 If we desugar the second do block, we get如果我们对第二个do块进行脱糖处理,我们会得到

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

Since this is the desugaring of the other one, it behaves the same, getting three lines before printing.因为这是另一个的脱糖,所以它的行为相同,在打印前得到三行。 There's another line of thought that arrives at the same answer, if you don't find this first one intuitive.如果您觉得第一个不直观,还有另一种思路可以得出相同的答案。 "Referential transparency", one of the defining features of Haskell, means exactly that you can replace a "reference" to something (that is, a variable name) with its definition, so the previous program should be exactly the same program as “引用透明”是Haskell的定义特性之一,准确的意思是你可以用它的定义替换对某个东西(即变量名)的“引用”,所以前面的程序应该和下面的程序完全一样

main = do
    getLine
    getLine
    getLine
    putStrLn "foo"

if we are taking the equation x = getLine seriously.如果我们认真对待方程x = getLine Okay, so we have a program that reads three lines and prints.好的,所以我们有一个读取三行并打印的程序。 What about this one?这个如何?

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

Get two lines and print.获取两行并打印。 And this one?还有这个?

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

Get one line and then print.得到一行然后打印。 Hopefully you see where this is going...希望你看到这是怎么回事......

main = do
    putStrLn "foo"
    where x = getLine

Get zero lines and then print, ie just print immediately!获取零行然后打印,即立即打印! I used where instead of let to make the opening example a bit more obvious, but you can pretty much always replace a where block with its let cousin without changing its meaning:我使用where而不是let来使开头的示例更加明显,但是您几乎总是可以用它的let表亲替换where块而不改变其含义:

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

Since we don't refer to x , we don't even need to name it:由于我们不引用x ,我们甚至不需要命名它:

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

and this is the desugaring of the code you wrote.这是您编写的代码的脱糖。

The first case is desugared like you expected:第一种情况像您预期的那样脱糖:

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

which is equivalent to这相当于

main = getLine >> putStrLn "foo"

In the second case,在第二种情况下,

main = do
    let _ = getLine
    putStrLn "foo"

is desugared as被脱糖为

main = let _ = getLine in putStrLn "foo"

Since the _ = getLine value is not needed to evaluate the RHS of the let expression, the compiler is free to ignore it and the IO effect is never executed, which is why you're not prompted for CLI input anymore.由于不需要_ = getLine值来评估let表达式的 RHS,因此编译器可以随意忽略它并且永远不会执行 IO 效果,这就是为什么不再提示您进行 CLI 输入的原因。

Even though both cases ignored the result of getLine the difference is that the first case evaluates getLine in an IO context while the second case evaluates getLine as a pure value.尽管这两种情况都忽略了getLine的结果,但区别在于第一种情况在IO上下文中评估getLine ,而第二种情况将getLine评估为纯值。 In IO the side-effects must executed and sequenced together, but in a pure context the compiler is free to ignore unused values.IO中,副作用必须一起执行和排序,但在纯上下文中,编译器可以自由地忽略未使用的值。

I wouldn't recommend doing this as it's not very idiomatic, but you could write something like我不建议这样做,因为它不是很惯用,但你可以写类似的东西

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

and use it like printIOString getLine并像printIOString getLine一样使用它

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

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

which by Haskell semantics is equivalent to Haskell 语义等同于

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

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

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

which can't be further simplified.无法进一步简化。

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

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