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

发布于 2025-01-11 14:52:01 字数 864 浏览 0 评论 0原文

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

main = do
    _ <- getLine
    putStrLn "foo"

我了解其正在执行的操作的基本要点。 getLine 返回一个 IO StringputStrLn 接受一个 String 并返回 IO () >,所以如果理论上我想打印用户在控制台中输入的内容,我基本上会使用 Monad 类中的 >>= 运算符。就我而言,我相信我的代码相当于 getLine >>> putStrLn "foo" 因为我放弃了 getLine 的返回值。

但是,如果我这样做怎么办?

main = do
    let _ = getLine
    putStrLn "foo"

在本例中,我们正在设置一种 lambda 来处理需要 IO String 的东西,对吗?我可以编写一个 printIOString 函数来打印用户的输入,这样就可以正常工作。然而,当我实际上没有使用该 IO String 时,程序的行为很奇怪...... getLine 甚至不提示我输入;程序只是打印出“foo”

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

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

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? I could write a printIOString function to print the user's input and that would work fine. When I'm not actually using that IO String, though, the program behaves strangely... getLine doesn't even prompt me for input; the program just prints out "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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

梦里泪两行 2025-01-18 14:52:01

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

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 来使开头的示例更加明显,但是您几乎总是可以将 where 块替换为其 < code>let 表亲而不改变其含义:

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

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

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

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

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

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

main = do
    getLine
    getLine
    getLine
    putStrLn "foo"

if we are taking the equation x = getLine seriously. 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, i.e. 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:

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

Since we don't refer to x, we don't even need to name it:

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

and this is the desugaring of the code you wrote.

猥琐帝 2025-01-18 14:52:01

第一种情况按照您的预期脱糖:

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

相当于

main = getLine >> putStrLn "foo"

在第二种情况下,

main = do
    let _ = getLine
    putStrLn "foo"

脱糖为

main = let _ = getLine in putStrLn "foo"

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

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

我不建议这样做,因为它不是很惯用,但您可以编写类似的内容

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

并像 printIOString getLine 一样使用它

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.

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. In IO the side-effects must executed and sequenced together, but in a pure context the compiler is free to ignore unused values.

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

坦然微笑 2025-01-18 14:52:01

根据 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 = f x),而实际上

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

这不能进一步简化。

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

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

(with x & f = f x), whereas indeed

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

which can't be further simplified.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文