我很難理解這一點。在寫符號時,以下兩行如何不同?「< - 」中的符號綁定
1. let x = expression
2. x <- expression
我看不到它。有時候一個人工作,一個人工作。但很少兩個。 「學習你一個哈斯克爾」說,<-
綁定右側的符號在左邊。但是,與簡單定義x
和let
有什麼不同呢?
我很難理解這一點。在寫符號時,以下兩行如何不同?「< - 」中的符號綁定
1. let x = expression
2. x <- expression
我看不到它。有時候一個人工作,一個人工作。但很少兩個。 「學習你一個哈斯克爾」說,<-
綁定右側的符號在左邊。但是,與簡單定義x
和let
有什麼不同呢?
的<-
語句將從單子提取值,並且let
語句將不。
import Data.Typeable
readInt :: String -> IO Int
readInt s = do
putStrLn $ "Enter value for " ++ s ++ ": "
readLn
main = do
x <- readInt "x"
let y = readInt "y"
putStrLn $ "x :: " ++ show (typeOf x)
putStrLn $ "y :: " ++ show (typeOf y)
運行時,程序會要求x的值,因爲一元行動readInt "x"
由<-
語句執行。它不會要求y的值,因爲readInt "y"
會被評估,但是不會執行生成的monadic動作。
Enter value for x: 123 x :: Int y :: IO Int
由於x :: Int
,你可以做正常的事情Int
用它。
putStrLn $ "x = " ++ show x
putStrLn $ "x * 2 = " ++ show (x * 2)
由於y :: IO Int
,你不能假裝這是一個普通的Int
。
putStrLn $ "y = " ++ show y -- ERROR
putStrLn $ "y * 2 = " ++ show (y * 2) -- ERROR
在let
窗體中,expression
是非單值的值,而<-
的右側是monadic表達式。例如,您只能在第二種綁定中進行I/O操作(類型爲IO t
)。詳細地說,這兩種形式可以大致翻譯爲(其中==>
示出了翻譯):
do {let x = expression; rest} ==> let x = expression in do {rest}
和
do {x <- operation; rest} ==> operation >>= (\ x -> do {rest})
在let
結合,表達可以是任何類型的,而你正在做的是給它一個名稱(其內部結構或模式匹配)。
在<-
版本,表達必須有m a
類型,其中m
是什麼單子的do
塊爲,因此在IO
單子,比如,這種形式的綁定必須有正確的IO a
類型的某個值 - 手邊。 a
部分(在monadic值內)是綁定到左側模式的部分。這使您可以在do
塊的有限範圍內提取monad的「內容」。
的do
符號,正如你可能已經讀過,只是語法糖在一元綁定運營商(>>=
和>>
)。 (本身沒有<-
)去糖至expression >>
。這只是給定義一個單子計算的長鏈提供了一個更方便的語法,否則它們往往會形成一個相當令人印象深刻的嵌套lambda表達式。
let
綁定完全不脫糖,真的。 do
塊中的let
塊和do
塊之外的let
之間的唯一區別在於do
版本不需要in
關鍵字來跟隨它;它所綁定的名稱隱含在do
塊的其餘部分的範圍內。
Haskell通過用形式類型IO a
表示命令式動作來表達與純函數式編程的副作用命令式編程:產生a
類型結果的命令式動作的類型。
一個這樣的後果是一個變量綁定到一個表達式的值,並將其綁定到執行的動作的結果是兩個不同的東西:
x <- action -- execute action and bind x to the result; may cause effect
let x = expression -- bind x to the value of the expression; no side effects
所以getLine :: IO String
是一個動作,該意味着它必須使用這樣的:
do line <- getLine -- side effect: read from stdin
-- ...do stuff with line
鑑於line1 ++ line2 :: String
是一個純粹的表達,並且必須以let
使用:
do line1 <- getLine -- executes an action
line2 <- getLine -- executes an action
let joined = line1 ++ line2 -- pure calculation; no action is executed
return joined
下面是一個簡單的例子,顯示你的差異。 考慮以下兩個簡單的表達式:
letExpression = 2
bindExpression = Just 2
您要檢索的信息數量2
。 這裏是你如何做到這一點:
let x = letExpression
x <- bindExpression
let
直接把價值2
在x
。 <-
從Just
中提取值2
並將其放入x
。
您可以用例子,爲什麼這兩個符號是不能互換的看到:
let x = bindExpression
將直接把價值Just 2
在x
。 x <- letExpression
將不會提取任何東西並放入x
。
let
只是給一個名稱賦值,或者對任意值匹配模式。
對於<-
,讓我們第一步從(不是真的)神祕IO
單子了,但考慮到有一個「容器」的概念單子,像列表或Maybe
。然後<-
不會超過「解包」該容器的元素。 「退回」的相反操作是return
。考慮以下代碼:
add m1 m2 = do
v1 <- m1
v2 <- m2
return (v1 + v2)
它「解壓縮」兩個容器中的元素,添加值在一起,並在同一單子再次將其包裝。它的工作原理與列表,以元素的所有可能的組合:
main = print $ add [1, 2, 3] [40, 50]
--[41,51,42,52,43,53]
事實上,在列表中的情況下,你可以寫,以及add m1 m2 = [v1 + v2 | v1 <- m1, v2 <- m2]
。但是,我們的版本Maybe
的作品,太:
main = print $ add (Just 3) (Just 12)
--Just 15
main = print $ add (Just 3) Nothing
--Nothing
現在IO
是沒有什麼不同的。這是一個單一價值的容器,但它是一種「危險的」不純的價值,就像病毒一樣,我們不能直接聯繫。在這裏,我們的玻璃容器是012塊玻璃,而<-
是內置的「手套」來操作裏面的東西。在return
的情況下,當我們準備好時,我們提供完整的,完整的容器(而不僅僅是危險的內容)。順便說一下,add
函數與IO
值(我們從文件或命令行或隨機生成器中獲得的值)一起使用。