2012-03-29 131 views
21

我很難理解這一點。在寫符號時,以下兩行如何不同?「< - 」中的符號綁定

1. let x = expression 
2. x <- expression 

我看不到它。有時候一個人工作,一個人工作。但很少兩個。 「學習你一個哈斯克爾」說,<-綁定右側的符號在左邊。但是,與簡單定義xlet有什麼不同呢?

回答

25

<-語句將從單子提取值,並且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 
8

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}) 
13

let結合,表達可以是任何類型的,而你正在做的是給它一個名稱(其內部結構或模式匹配)。

<-版本,表達必須有m a類型,其中m是什麼單子的do塊爲,因此在IO單子,比如,這種形式的綁定必須有正確的IO a類型的某個值 - 手邊。 a部分(在monadic值內)是綁定到左側模式的部分。這使您可以在do塊的有限範圍內提取monad的「內容」。

do符號,正如你可能已經讀過,只是語法糖在一元綁定運營商(>>=>>)。 (本身沒有<-)去糖至expression >>。這只是給定義一個單子計算的長鏈提供了一個更方便的語法,否則它們往往會形成一個相當令人印象深刻的嵌套lambda表達式。

let綁定完全不脫糖,真的。 do塊中的let塊和do塊之外的let之間的唯一區別在於do版本不需要in關鍵字來跟隨它;它所綁定的名稱隱含在do塊的其餘部分的範圍內。

2

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 
2

下面是一個簡單的例子,顯示你的差異。 考慮以下兩個簡單的表達式:

letExpression = 2 
bindExpression = Just 2 

您要檢索的信息數量2。 這裏是你如何做到這一點:

let x = letExpression 
x <- bindExpression 

let直接把價值2x<-Just中提取值2並將其放入x

您可以用例子,爲什麼這兩個符號是不能互換的看到:

let x = bindExpression將直接把價值Just 2xx <- letExpression將不會提取任何東西並放入x

3

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值(我們從文件或命令行或隨機生成器中獲得的值)一起使用。

相關問題