2017-05-23 56 views
1

對不起新問題,但Haskell如何知道不要將參照透明度應用於readLn或當putStrLn - 兩次相同的字符串?是否因爲涉及IO? IOW,編譯器不會將參照透明度應用於返回IO的函數嗎?IO vs參考透明度

回答

2

的IO類型定義爲:

newtype IO a = IO (State# RealWorld -> (# State# RealWorld, a #)) 

通知是非常相似的國家單子其中狀態是現實世界的狀態,所以恕我直言,你能想到的IO作爲引用透明和純潔,有什麼不純是Haskell的運行時(翻譯)的那運行你的IO動作(代數)。

看一看Haskell的維基,它更詳細地解釋了IO:IO Inside

+1

以這種方式思考'IO' monad是一種可怕的方式來思考它,而這個特定的視圖是GHC的實現細節,並且需要hack編譯器才能正常工作。要看看這是多麼可怕,請考慮此模型中的併發性。取決於你認真對待它的方式,你會發現它提供的絕對不是解釋性的力量,或者它轉化爲形而上學。基於延續的或免費的基於monad的模型更加清晰和準確。對於這個問題,儘管'IO'實際上是什麼不相關,只是它是一個數據類型。 –

+0

對不起,我不想強​​調這種強調,我也相信知道內部IO或者它是否是Monad是無關緊要的,我的目標是證明從Haskell程序員的角度來看,函數返回IO包裝類型與其他任何函數一樣都是透明的,是運行時對IO類型的評估,而不是。這個*做作的例子*表明,如果我們可以從運行時隱藏IO計算,那麼這些IO函數什麼也不做:https://gist.github.com/anler/d2633d55a26e11d3805c776efba22eed –

2

由於返回值被裹成IO,你不能重複使用它們,除非您將它們「拉」出來,有效地運行IO動作:

readLn :: IO String 

twoLines = readLn ++ readLn -- can't do this, because (++) works on String's, not IO String's 

twoLines' = do 
    l1 <- readLn 
    l2 <- readLn -- "pulling" out of readLn causes console input to be read again 
    return (l1 ++ l2) -- so l1 and l2 have different values, and this works 
1

排序的。您可能已經聽說IO是monad,這意味着其中包含的值必須使用monadic操作,例如綁定(>>=),順序組合(>>)和return。所以,你可以寫這樣的問候程序:

prompt :: IO String 
prompt = putStrLn "Who are you?" >> 
     getLine >>= 
     \name -> 
      return $ "Hello, " ++ name ++ "!" 

main :: IO() 
main = prompt >>= 
     putStrLn 

你更容易看到這個用等價do符號,這是寫完全相同的程序的另一種方式。在這種情況下,我認爲非糖型版本更清楚地表明,計算是一系列與>>>>=鏈接在一起的語句,其中我們使用>>當我們想要拋出上一步的結果時,並且>>=當我們想要將結果傳遞給鏈中的下一個函數。如果我們需要給這個結果一個名字,我們可以把它作爲一個參數來捕獲,就像在prompt裏面的lambda表達式\name ->一樣。如果我們需要將簡單的String轉換爲IO String,我們使用return

do符號等效,順便說一下,就是:

prompt :: IO String 
prompt = do 
    putStrLn "Who are you?" 
    name <- getLine 
    return $ "Hello, " ++ name ++ "!" 

main :: IO() 
main = do 
    message <- prompt 
    putStrLn message 

那麼它是怎樣知道main,它沒有返回值,是不是引用透明,而且prompt,它返回一個IO String,是都不是?對於IO有一些特殊之處,或者至少IO缺少:對於許多其他單子(如StateMaybe),有一種方法可以在monad中執行懶計算並丟棄包裝,從而獲得純淨值。您可以在其中聲明一個State Int monad,do確定性,順序,有狀態的計算,然後使用evalState獲得純粹的Int結果。您可以執行Maybe Char計算,例如搜索字符串中的字符,檢查它是否有效,如果是這樣,請讀回純粹的Char

IO,你不能這樣做。如果你有一個IO String,你可以用它做的是將其綁定到一個IO函數,接受String參數,如PutStrLn,或者將它傳遞給具有IO String參數的函數。如果您再次致電prompt,它不會默默地給你同樣的結果;它會再次運行。如果你告訴它暫停幾毫秒,它不會懶惰地等待,直到你稍後在程序中需要一些返回值來做到這一點。如果它返回一個空值,如IO(),編譯器不會通過返回該常量來優化它。

這種內部工作方式是將對象與每個調用的不同狀態的世界參數一起包裝在一起。這意味着對getLine的兩個不同調用取決於世界的不同狀態,並且main的返回值需要計算世界的最終狀態,這取決於所有先前的操作。

8

您需要區分評估執行

如果評估 2 + 7,如果更換一個表達式,其值9與另一個,不同的表達也計算爲9的結果是9,則該程序的含義並沒有改變。這是參照透明度保證。我們可以將幾個共享表達式減少到9個,或將共享表達式複製到多個副本中,並且程序的含義不會改變。 (性能可能,但不是最終結果。)

如果你評估readLn,它評估爲一個I/O命令對象。你可以把它想象成一個數據結構,描述你想要執行什麼I/O操作。但是對象本身就是數據。如果您評估readLn兩次,它將返回兩次相同的I/O命令對象。您可以將多個副本合併爲一個;你可以將一個副本分成幾個副本。它不會改變程序的含義。

現在,如果你想執行的I/O行動,那是不同的事情。顯然,I/O操作需要按照程序指定的方式執行,而不是隨機重複或重新排列。但沒關係,因爲這不是Haskell表達式評估引擎。您可以假裝Haskell運行時運行main,它構建了一個代表整個程序的巨大I/O命令對象。然後,Haskell運行時將按照指定的順序讀取此數據結構並執行它請求的I/O操作。 (實際上沒有它是如何工作的,但一個有用的心智模式。)

通常不需要費心思考之間的嚴格分離評估readLn得到一個I/O命令對象,然後執行的從而導致I/O命令對象獲得結果。但嚴格來說,它的概念就是它的功能。 (您可能也聽說過I/O「形成單子」,這只是一種奇怪的說法,即有一組操作符用於將I/O命令對象一起更改爲更大的I/O命令對象。理解評估和執行之間的分離並不重要。)