Haskell和懒惰的Monads评估

时间:2011-10-17 12:49:44

标签: haskell monads lazy-evaluation

在玩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 ())
显然,第一个参数没有打印在stdout上,因为没有评估第一个monad IO的内容。所以如果我加入两个monad,它就可以了。

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

请有人,请为我澄清一下吗? (或给我一个指针)

2 个答案:

答案 0 :(得分:11)

Haskell 2010报告(语言定义)says

  

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

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

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

问题是您将fmapprint结合使用。查看Functor的{​​{1}}实例的定义:

IO

你可以看到,这使你的表达式等同于instance Functor IO where fmap f x = x >>= (return . f) 。要执行您最初的预期,只需删除不必要的(head <$> getArgs >>= return . print)

return

或等同地:

head <$> getArgs >>= print

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

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

答案 1 :(得分:4)

您有head <$> getArgs :: IO Stringprint :: Show a => a -> IO (),即monad中的值和从普通值到monad的函数。用于组成此类事物的函数是monadic绑定运算符(>>=) :: Monad m => m a -> (a -> m b) -> m b

所以你想要的是

main = head <$> getArgs >>= print

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