在玩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
请有人,请为我澄清一下吗? (或给我一个指针)
答案 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 ())
问题是您将fmap
与print
结合使用。查看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 String
和print :: 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
不是纯粹的。