2013-06-13 91 views
7

假設您在haskell中有一個無用函數,它在代碼中多次使用。它總是隻評估一次?我已經測試了以下代碼:Haskell中空函數的評估

sayHello :: Int 
sayHello = unsafePerformIO $ do 
    putStr "Hello" 
    return 42 

test :: Int -> [Int] 
test 0 = [] 
test n = (sayHello:(test (n-1))) 

當我打電話測試10,它寫道:「你好」 ONY一次,所以它表示第一次評估之後,存儲功能的結果。我的問題是,它有保證嗎?我會在不同的編譯器中得到相同的結果嗎?

編輯 我使用unsafePerformIO的原因是爲了檢查sayHello是否被多次評估。我不會在我的程序中使用它。通常我希望sayHello在每次評估時都有完全相同的結果。但它是一個耗時的操作,所以我想知道這是否可以訪問這種方式,或者如果它應該徘徊無論它以確保它不會多次評估真實需要一個參數,即傳遞:

test _ 0 = [] 
test s n = (s:(test (n-1))) 
... 
test sayHello 10 

根據這個答案應該使用。

回答

17

沒有這樣的東西作爲無功功能。 Haskell中的一個函數只有一個參數,並且總是有類型... -> ...sayHello是一個值 - Int - 但不是函數。有關更多信息,請參閱this article

保證:不,你沒有得到任何保證。 Haskell報告指出,Haskell不是嚴格的 - 所以你知道事情最終會減少什麼價值 - 但不是任何特定的評估策略。 GHC通常使用的評估策略是lazy evaluation,即對共享進行非嚴格評估,但並未對此做出有力保證 - 優化程序可能會對代碼進行隨機混排,以便對其進行多次評估。

也有各種例外 - 例如,foo :: Num a => a是多態的,所以它可能不會被共享(它被編譯爲實際函數)。有時候,一個純粹的值可能會被多個線程同時評估(在這種情況下不會發生這種情況,因爲unsafePerformIO明確使用noDuplicate來避免它)。所以,當你編程時,你通常可以預期懶惰,但如果你想要任何形式的保證,你必須非常小心。報告本身不會給你任何東西如何你的程序被評估。

unsafePerformIO當然,在擔保方式上給予您更少。有一個原因叫做「不安全」。

5

sayHello這樣的頂級無參數函數被稱爲常量應用表單,並且總是被記憶(至少在GHC中 - 參見http://www.haskell.org/ghc/docs/7.2.1/html/users_guide/profiling.html)。你將不得不採取一些竅門,比如傳遞虛擬參數並將優化轉化爲而不是在全球共享一個CAF。

編輯:報價從上面的鏈接 -

Haskell是一個懶惰的語言,只有永遠 一次評估某些表達式。例如,如果我們編寫: x = nfib 25那麼x將只被評估一次(如果有的話),並且後續要求x會立即看到緩存的結果。 定義x被稱爲CAF(Constant Applicative Form),因爲 它沒有參數。

1

如果你想「你好」印刷N次,你需要刪除unsafePermformIO,所以運行時將知道它不能優化掉重複調用putStr。我不清楚你是否想返回int列表,所以我寫了兩個版本的測試,其中一個返回(),一個[Int]。

sayHello2 :: IO Int 
sayHello2 = do 
    putStr "Hello" 
    return 42 

test2 :: Int -> IO() 
test2 0 = return() 
test2 n = do 
    sayHello2 
    test2 (n-1) 

test3 :: Int -> IO [Int] 
test3 0 = return [] 
test3 n = do 
    r <- sayHello2 
    l <- test3 (n-1) 
    return $ r:l