我想這可能是一個有爭議的話題,因為我深入了解了語言設計,而且我知道周圍的人不會喜歡這樣,因為他們誤解了我否認他們喜歡的東西的某些優點。
為什么 Haskell 需要IO/Actions即使它是惰性求值?
我理解 IO/Actions 機制對于保持函式式編程的所謂“純粹性”的價值,如果它是用于 C、JavaScript 或任何其他急切評估語言的話。
事實上,我確實IO ()在熱切評估的 Typescript 中進行了模擬/實作,然后我想“好吧,很酷,但是為什么 Haskell 需要這個??”
Haskell 默認是惰性的,因此即使函式也定義為
print==console.log
在 JavaScript 語法中,在 Haskell 中,因為它是惰性的,print除非它連接到main :: IO ().
有什么想法嗎?
編輯:
顯然,這個問題源于我的完全誤解。
在 Haskell 中,它定義為
print==console.log
print :: Show a => a -> IO () -- Defined in ‘System.IO’
我只是誤解為好像定義為
print :: Show a => a -> _ -> IO ()
因為在熱切的評估中需要如此模仿。
uj5u.com熱心網友回復:
你把事情搞混了!盡管懶惰, Haskell 不需要它IO,它因為懶惰而需要它。
讓我們想象一下我們沒有IO(或者,等效地,所做的一切都IO被 隱式包裝unsafePerformIO)。因此,例如,我可能會寫:
main = print (readLn readLn)
這將從用戶那里獲得兩行輸入,將它們決議為數字,將它們相加并列印結果。好的!到目前為止沒有問題。現在我決定要實作一種小語言。我想做的事情是從用戶那里讀取一對——比如 5 個——變數/值對,將它們粘貼在 aMap中,然后從用戶那里讀取一個可能提到這些變數的運算式。所以與用戶的互動可能看起來像
> 5
> 32
> 17
> -6
> 72
> (x1 x4) * (x0 x3)
< -104
其中>標記我輸入的<行并標記程式列印的行。答案是 -104,因為 x1=32、x4=72、x0=5 和 x3=-6。不使用 x2=17 的系結。好,我們寫吧。
import qualified Data.Map as M
interpret :: M.Map String Int -> String -> Int
interpret = {- not relevant, really... right? -}
main = interpret env expr where
env = M.fromList [("x0", readLn), ("x1", readLn), ("x2", readLn), ("x3", readLn), ("x4", readLn)]
expr = getLine
好的,現在,小測驗:這個程式是做什么的?好吧,如果我們認真對待懶惰,那么所有這些getLines 都會被推遲到有人真正看到它們為止。如果有人在看,那是誰?是interpret!所以,要知道這個程式做什么,我們實際上必須知道做什么interpret。好的,讓我們開始填寫:
interpret env s = case parseExpr s of
Just expr -> evaluateArithmetic (replaceVariables env expr)
Nothing -> 404 -- lol
...aaaand,現在我們遇到了麻煩。實際上,有很多原因。因為首先要做的interpret是評估s,這意味著用戶鍵入的第一行實際上扮演了運算式的角色,而不是最后一行。所以這有點不幸,但是好吧,也許我們只是認為這很好,并重新構想我們理想的互動以符合這些實作細節:
> (x1 x4) * (x0 x3)
> 5
> 32
> 17
> -6
> 72
< -104
但即使我們放棄了將表情放在最后的夢想,我們仍然有麻煩。因為看看做什么replaceVariables:
data Expr = Lit Int | Var String | Add Expr Expr | Times Expr Expr
replaceVariables env (Lit n) = Lit n
replaceVariables env (Var v) = Lit (env M.! v)
replaceVariables env (Add x y) = Add (replaceVariables env x) (replaceVariables env y)
replaceVariables env (Times x y) = Times (replaceVariables env x) (replaceVariables env y)
Did you spot it? With the expression the user typed in, x1 is the first variable it tries to replace -- meaning that it is the first readLn that gets executed, and instead of being 32, the second number we entered, as we intended, it is 5, the first number we entered. Similarly, x4 becomes 32 instead of 72, etc. and we get a just plain wrong answer. (Also, the program replies after we enter the fourth number without waiting for the fifth. But maybe that's not such a big deal.)
So this is the crux of the problem: without IO, the programmer has much less control over what order interactions with the user happen in. There's a follow-on problem that we didn't explore here, which is that not only is there little control, but that refactoring can change the interface -- if we made replaceVariables swap the arguments to Add for some reason, even though this really seems like a change that shouldn't affect anything, it makes the order that lines get read from the user even more different and confusing!
This is the core problem that IO solves. The implementation of (>>=) adds a data dependency that prevents later computations from executing until earlier ones finish. This means that when we write
main = readLn >>= \x -> {- rest of the program -}
we can be sure that x contains the contents of the first line the user types in, not some other line determined by the structure of the entire rest of the program.
Having to understand entire programs at once to know what small chunks of it do just doesn't work at scale!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/434068.html
標籤:哈斯克尔
