【问题标题】:Haskell and lazy Monads evaluationHaskell 和惰性 Monads 评估
【发布时间】:2011-10-17 12:49:43
【问题描述】:

在玩 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

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

【问题讨论】:

    标签: haskell monads lazy-evaluation


    【解决方案1】:

    Haskell 2010 报告(语言定义)says

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

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

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

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

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

    您可以看到这使您的表达式等同于(head &lt;$&gt; getArgs &gt;&gt;= return . print)。做你原本打算的,只需删除不必要的return

    head <$> getArgs >>= print
    

    或者,等效地:

    print =<< head <$> getArgs
    

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

    还请注意,这与惰性求值无关,而是与纯度有关 - 从语义上讲,您的程序是一个纯函数,它返回一个类型为 IO a 的值,然后由运行时系统解释。由于您的内部 IO 操作不是此计算的一部分,因此运行时系统只是将其丢弃。 Simon Peyton Jones 的"Tackling the Awkward Squad" 的第二章很好地介绍了这些问题。

    【讨论】:

    • 非常感谢您的详尽回复。
    • 所以基本上我所做的就是执行getArgs(外部IO)并将结果放入从未执行过的东西中(因为被main忽略了)。
    • 正确。您的代码执行head &lt;$&gt; getArgs,然后将其结果提供给return . print,其类型为a -&gt; IO (IO ())
    • 虽然,head 没有被评估,因为它的结果是不需要的,所以你的代码永远不会产生“空列表”异常。您可以通过将getArgs 更改为getLine 来进行测试。
    【解决方案2】:

    您有 head &lt;$&gt; getArgs :: IO Stringprint :: Show a =&gt; a -&gt; IO (),即 monad 中的值和从普通值到 monad 的函数。用于组合这些东西的函数是一元绑定运算符(&gt;&gt;=) :: Monad m =&gt; m a -&gt; (a -&gt; m b) -&gt; m b

    所以你想要的是

    main = head <$> getArgs >>= print
    

    (&lt;$&gt;) 又名fmap 具有Functor f =&gt; (a -&gt; b) -&gt; f a -&gt; f b 类型,因此当您想将 pure 函数应用于 monad 中的某个值时它很有用,这就是它与 @ 一起使用的原因987654328@ 但不是print,因为print 不纯。

    【讨论】: