2012-03-29 20 views
21

Trudno mi to zrozumieć. Podczas pisania w notacji, w jaki sposób różnią się następujące dwie linie?"Wiązania <-" w notacji

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

Nie widzę tego. Czasami jeden działa, czasami drugi. Ale rzadko oba. "Learn you a haskell" mówi, że <- wiąże prawą stronę z symbolem po lewej stronie. Ale jak to się różni od zwykłego definiowania x z ?

Odpowiedz

25

Stwierdzenie <- wychwycenia wartości z monadzie, a rachunek let zostanie nie.

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) 

Po uruchomieniu program poprosi o wartości x, ponieważ monadycznego działanie readInt "x" jest wykonywany przez oświadczenie <-. Nie będzie pytać o wartość y, ponieważ jest oceniany readInt "y", ale wynikowa akcja monadyczna nie jest wykonywana.

 
Enter value for x: 
123 
x :: Int 
y :: IO Int 

Od x :: Int można zrobić normalne Int rzeczy z nim.

putStrLn $ "x = " ++ show x 
putStrLn $ "x * 2 = " ++ show (x * 2) 

Od y :: IO Int, nie można udawać, że jest to regularne Int.

putStrLn $ "y = " ++ show y -- ERROR 
putStrLn $ "y * 2 = " ++ show (y * 2) -- ERROR 
8

W formularzu let, expression jest wartością niemagadyczną, natomiast prawa strona z <- jest monadycznym wyrażeniem. Na przykład można tylko operację we/wy (typu IO t) w drugim rodzaju powiązania. W szczególności, obie postacie można w przybliżeniu przekształcić w (gdzie ==> przedstawia tłumaczenia):

do {let x = expression; rest} ==> let x = expression in do {rest} 

i

do {x <- operation; rest} ==> operation >>= (\ x -> do {rest}) 
13

W let wiążącej, wyrażenie może mieć dowolny typ, a wszystko co robisz jest nadając mu imię (lub dopasowywania wzoru na jego wewnętrznej struktury).

W wersji <- wyrażenie musi mieć typ m a, gdzie m jest cokolwiek monada blok do jest. Więc w IO monady, na przykład, wiązania tej postaci musi mieć jakąś wartość typu IO a po prawej z drugiej strony. Część a (wewnątrz monadycznej wartości) jest związana z wzorem po lewej stronie. To pozwala wyodrębnić "zawartość" monady w ograniczonym zakresie bloku do.

Zapis do jest, jak można przeczytać, po prostu cukier syntaktyczny ciągu monadycznych operatorów wiążących (>>= i >>). x <- expression de-cukry do expression >>= \x -> i expression (przez siebie, bez <-) de-cukry do expression >>. Daje to wygodniejszą składnię do definiowania długich łańcuchów monadycznych obliczeń, które w przeciwnym razie mają tendencję do budowania raczej imponującej masy zagnieżdżonych lambd.

let Wiązania w ogóle nie usuwają cukru. Jedyną różnicą między let w bloku do i let poza blokiem do jest to, że wersja do nie wymaga słowa kluczowego in, aby go śledzić; nazwy, które łączy, są domyślnie w zakresie dla reszty bloku do.

2

Haskell godzi efektywne z efektem bocznym programowanie imperatywne z czystym programowaniem funkcjonalnym, reprezentując działania imperatywne o typie formularza IO a: typ imperatywnej akcji, która daje wynik typu a.

Jedną z konsekwencji jest to, że wiązanie zmiennej wartości wyrażenia i wiązania go w wyniku realizacji działania są dwie różne rzeczy:

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 

Więc getLine :: IO String jest to działanie, które środki muszą być stosowane tak:

do line <- getLine -- side effect: read from stdin 
    -- ...do stuff with line 

Zważywszy line1 ++ line2 :: String to czysta ekspresja i musi być używany z 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

Oto prosty przykład pokazujący różnicę. Rozważmy dwa następujące prostych wyrażeń:

letExpression = 2 
bindExpression = Just 2 

Informacje, które próbują odzyskać to numer 2. Oto jak to zrobić:

let x = letExpression 
x <- bindExpression 

let bezpośrednio stawia wartość 2 w x. <- wyodrębnia wartość 2 z Just i umieszcza ją w x.

Widać z tego przykładu, dlaczego te dwa oznaczenia nie są wymienne:

let x = bindExpression będzie bezpośrednio umieścić wartość Just 2 w x. x <- letExpression nie będzie mieć niczego do wyodrębnienia i wstawienia x.

3

let po prostu przydziela nazwę lub dopasowuje wzorce do dowolnych wartości.

Dla <-, niech najpierw krok od (naprawdę nie) tajemnicze IO monady, ale uważają, że monady mają pojęcie „pojemnik”, takie jak listy lub Maybe. Wtedy <- nie zajmuje się "rozpakowaniem" elementów tego kontenera. Odwrotną operacją "odkładania" jest return. Rozważ ten kod:

add m1 m2 = do 
    v1 <- m1 
    v2 <- m2 
    return (v1 + v2) 

"Rozpakowuje" elementy dwóch pojemników, dodaje wartości razem i zawija je ponownie w tej samej monadzie.Współpracuje z list, z uwzględnieniem wszystkich możliwych kombinacji elementów:

main = print $ add [1, 2, 3] [40, 50] 
--[41,51,42,52,43,53] 

W rzeczywistości w przypadku list można napisać także add m1 m2 = [v1 + v2 | v1 <- m1, v2 <- m2]. Ale nasza wersja współpracuje z Maybe s, zbyt:

main = print $ add (Just 3) (Just 12) 
--Just 15 
main = print $ add (Just 3) Nothing 
--Nothing 

Teraz IO nie różni się w ogóle. Jest to pojemnik o pojedynczej wartości, ale jest to "niebezpieczna" nieczysta wartość, taka jak wirus, którego nie wolno nam bezpośrednio dotykać. do -Block jest tutaj naszą szklaną obudową, a <- są wbudowanymi "rękawicami" do manipulowania przedmiotami wewnątrz. Dzięki return dostarczamy pełny, nienaruszony kontener (a nie tylko niebezpieczną zawartość), kiedy jesteśmy gotowi. Nawiasem mówiąc, funkcja add działa również z wartościami IO (które otrzymaliśmy z pliku lub z wiersza poleceń lub generatora losowego ...).