2011-10-17 51 views
7

在玩單子時,我經常遇到評價問題。現在,我理解懶惰評估的基本概念,但我沒有看到單子在Haskell中如何被懶惰地評估。哈斯克爾和懶Monads評價

考慮下面的代碼

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()) 

所以顯然,第一個參數不被打印在標準輸出,因爲第一單子的內容IO不評估。所以如果我加入這兩個monad,它就會起作用。

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

請問誰能爲我澄清一下? (或給我一個指針)

回答

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實例的定義IO

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

你可以看到,這使你的表情相當於(head <$> getArgs >>= return . print)。做你原意,只是刪除不必要的return

head <$> getArgs >>= print 

,或等效:

print =<< head <$> getArgs 

注意,在Haskell IO操作是就像其他值 - 它們可以被傳遞和返回從函數中,存儲在列表和其他數據結構中,等等。一個IO操作不被評估,除非它是主要計算的一部分。要將IO操作「粘合」在一起,請使用>>>>=而不是fmap(它通常用於在某些「框」中映射函數的值 - 在您的示例中爲IO)。

還要注意的是,這並不是懶惰的評估,而是純粹性 - 在語義上,您的程序是一個純函數,返回值爲IO a的值,然後由運行時系統解釋。由於您的內部IO操作不是此計算的一部分,因此運行時系統會將其丟棄。這些問題的一個很好的介紹是西蒙佩頓瓊斯的第二章"Tackling the Awkward Squad"

+0

非常感謝您的詳盡回覆。 – Jack 2011-10-17 14:18:51

4

您有head <$> getArgs :: IO Stringprint :: Show a => a -> IO(),即monad中的值和從plain值到monad的函數。用於組成這些事物的函數是一元綁定運算符(>>=) :: Monad m => m a -> (a -> m b) -> m b

所以,你想要的是

main = head <$> getArgs >>= print 

(<$>)又名fmap有型Functor f => (a -> b) -> f a -> f b,所以當你想在一個單子一個功能適用於某些價值是有用的,這就是爲什麼它的工作原理與head但不與print,因爲print是不純的。