Haskell 和惰性 Monads 评估

发布于 2024-12-10 11:39:55 字数 634 浏览 0 评论 0原文

在使用 monad 时,我经常遇到评估问题。现在,我了解了延迟求值的基本概念,但我不明白如何在 Haskell 中延迟求值 monad。

考虑以下代码

module Main where
import Control.Monad
import Control.Applicative
import System

main = print <$> head <$> getArgs

在我看来,主函数应该打印第一个控制台参数,但事实并非如此。

我知道

getArgs :: IO [String]
head <$> getArgs :: IO String
print <$> (head <$> getArgs) :: IO (IO ())
main :: IO (IO ())

很明显,第一个参数没有打印在标准输出上,因为第一个 monad IO 的内容没有被评估。所以如果我加入这两个单子,它就会起作用。

main = join $ print <$> head <$> getArgs

有人可以帮我澄清一下吗? (或者给我指点)

While playing with monads I often incur in problems of evaluation. Now, I understand the basic concepts of lazy evaluation, but I don't get how monads are lazily evaluated in Haskell.

Consider the following code

module Main where
import Control.Monad
import Control.Applicative
import System

main = print <
gt; head <
gt; getArgs

In my mind it should the main function should print the first console argument, but it doesn't.

I know that

getArgs :: IO [String]
head <
gt; getArgs :: IO String
print <
gt; (head <
gt; getArgs) :: IO (IO ())
main :: IO (IO ())

so apparently, the first argument is not printed on the stdout because the content of the first monad IO is not evaluated. So if I join the two monads, it works.

main = join $ print <
gt; head <
gt; getArgs

Would anyone, please, clarify it for me? (or give me a pointer)

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

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

发布评论

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

评论(2

少跟Wǒ拽 2024-12-17 11:39:55

Haskell 2010 报告(语言定义)

程序的值是模块中标识符main的值
Main,它必须是某种 τ 类型的 IO τ 类型的计算。
当程序执行时,计算main
执行,其结果(τ类型)被丢弃。

您的 main 函数的类型为 IO (IO ())。上面的引用意味着仅评估外部操作 (IO (IO ())),并丢弃其结果 (IO ())。你是怎么到这里来的?让我们看看 print <$> 的类型:

> :t (print <
gt;)
(print <
gt;) :: (Show a, Functor f) => f a -> f (IO ())

所以问题是您将 fmapprint 结合使用。查看 IOFunctor 实例的定义:

instance  Functor IO where
   fmap f x = x >>= (return . f)

您可以看到,这使得您的表达式相当于 (head <$> getArgs >>=返回。要执行您最初的意图,只需删除不必要的 return

head <
gt; getArgs >>= print

或者,等效地:

print =<< head <
gt; getArgs

请注意,Haskell 中的 IO 操作就像其他值一样 - 它们可以传递给函数并从函数返回,存储在列表中,其他数据结构等。除非 IO 操作是主计算的一部分,否则不会对其进行评估。要将 IO 操作“粘合”在一起,请使用 >>>>=,而不是 fmap(通常用于映射pure 函数作用于某些“盒子”中的值 - 在您的情况下,IO)。

另请注意,这与惰性求值无关,而是与纯度有关 - 从语义上讲,您的程序是一个纯函数,它返回 IO a 类型的值,然后由运行时系统进行解释。由于您的内部 IO 操作不是此计算的一部分,因此运行时系统只会丢弃它。 Simon Peyton Jones 的第二章对这些问题做了很好的介绍 “解决尴尬的小队”

Haskell 2010 Report (the language definition) says:

The value of the program is the value of the identifier main in module
Main, which must be a computation of type IO τ for some type τ.
When the program is executed, the computation main is
performed, and its result (of type τ) is discarded.

Your main function has type IO (IO ()). The quote above means that only the outer action (IO (IO ())) is evaluated, and its result (IO ()) is discarded. How did you get here? Let's look at the type of print <$>:

> :t (print <
gt;)
(print <
gt;) :: (Show a, Functor f) => f a -> f (IO ())

So the problem is that you used fmap in conjunction with print. Looking at the definition of Functor instance for IO:

instance  Functor IO where
   fmap f x = x >>= (return . f)

you can see that that made your expression equivalent to (head <$> getArgs >>= return . print). To do what you originally intended, just remove the unnecessary return:

head <
gt; getArgs >>= print

Or, equivalently:

print =<< head <
gt; getArgs

Note that IO actions in Haskell are just like other values - they can be passed to and returned from functions, stored in lists and other data structures, etc. An IO action is not evaluated unless it's a part of the main computation. To "glue" IO actions together, use >> and >>=, not fmap (which is typically used for mapping pure functions over values in some "box" - in your case, IO).

Note also that this has to do not with lazy evaluation, but purity - semantically, your program is a pure function that returns a value of type IO a, which is then interpreted by the runtime system. Since your inner IO action is not part of this computation, the runtime system just discards it. A nice introduction to these issues is the second chapter of Simon Peyton Jones's "Tackling the Awkward Squad".

笑咖 2024-12-17 11:39:55

你有 head <$>; getArgs :: IO Stringprint :: 显示 =>一个-> IO(),即 monad 中的值以及从普通值到 monad 的函数。用于组成这些东西的函数是一元绑定运算符 (>>=) :: Monad m =>妈-> (a→mb)→ m b

所以你想要的是

main = head <
gt; getArgs >>= print

(<$>) 又名 fmap 具有类型 Functor f =>; (a→b)→发-> f b,因此当您想要将函数应用于 monad 中的某个值时,它非常有用,这就是为什么它适用于 head 而不适用于 < code>print,因为 print 不是纯粹的。

You have head <$> getArgs :: IO String and print :: Show a => a -> IO (), i.e. a value in a monad and a function from a plain value to a monad. The function used to compose such things is the monadic bind operator (>>=) :: Monad m => m a -> (a -> m b) -> m b.

So what you want is

main = head <
gt; getArgs >>= print

(<$>) aka fmap has the type Functor f => (a -> b) -> f a -> f b, so it is useful when you want to apply a pure function to some value in a monad, which is why it works with head but not with print, since print is not pure.

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