Я не понимаю, когда я должен использовать let
и когда я должен использовать привязку <-
.
В чем разница между блоками ‹- и let внутри?
Ответы (3)
let
дает имя результату вызова функции.
<-
связывает результат монадической операции в текущей монаде с именем.
Они совсем другие. Используйте let
для результатов функций вне монады, т.е. обычных чистых функций. Используйте <-
для всего монадического, так как он «разворачивает» результат монады и позволяет вам получить значение внутри него.
Например:
предположим функцию ввода-вывода со следующей сигнатурой
frobnicate :: String -> IO Bool
и чистая функция
dothing :: Bool -> Bool
мы можем сделать это
main :: IO ()
main = do
x <- frobnicate "Hello"
let y = frobnicate "Hello"
-- z <- dothing x
let z = dothing x
return ()
И мы знаем, что x :: Bool
, потому что Bool
было извлечено для нас из результата операции IO
(операция выполняется, и результат называется x
, поэтому мы можем использовать его позже).
Мы также знаем, что y :: IO Bool
— операция не была запущена, это потенциал для операции ввода-вывода в будущем. Таким образом, единственная полезная вещь, которую мы можем сделать с y
, это запустить его позже, связать результат и таким образом получить Bool
внутри, но после let
этот Bool
еще даже не существует.
Третья строка закомментирована, потому что она не будет компилироваться — вы не можете выполнить монадическую привязку к операции, которой нет в соответствующей монаде. dothing
ничего не возвращает IO
, поэтому вы не можете связать его внутри функции IO ()
.
Четвертая строка проста: z
делается результатом dothing x
, где x
было значением, развернутым при выполнении frobnicate "Hello"
ранее.
Все это просто синтаксический сахар для «настоящих» монадных операций внизу, так что это расширяется (без закомментированной части) до чего-то вроде
main = frobnicate "Hello" >>= (\x -> let y = frobnicate "Hello"
z = dothing x
in return ())
Пример, конечно, не имеет никакого смысла, но, надеюсь, он показывает, где let
и <-
различаются в нотации do
.
TL;DR: используйте <-
для присвоения имен результатам монадических операций, let
для присвоения имен всему остальному.
<-
соответствует >>=
(bind
), где let
соответствует fmap
в блоке do
.
Пример взят из здесь:
do x1 <- action1 x0
x2 <- action2 x1
action3 x1 x2
-- is equivalent to:
action1 x0 >>= \ x1 -> action2 x1 >>= \ x2 -> action3 x1 x2
action1
, action2
и action3
все возвращают некоторую монаду, скажем:
action1 :: (Monad m) => a -> m b
action2 :: (Monad m) => b -> m c
action3 :: (Monad m) => b -> c -> m d
Вы можете переписать привязки let как таковые:
-- assume action1 & action3 are the same
-- but action2 is thus:
action2 :: b -> c
do
x1 <- action1 x0
let x2 = action2 x1
action3 x1 x2
do
x1 <- action1 x0
x2 <- return & action2 x1
action3 x1 x2
-- of course it doesn't make sense to call action2
-- an action anymore, it's just a pure function.
Хороший пример для визуализации того, что делает <-
:
do
a <- ['a'..'z']
b <- [1..3]
pure (a,b)
Вы можете попробовать это в онлайн-REPL по адресу try.frege-lang.org (вы можете ввести это как одна строка:
do { a <- ['a'..'z']; b <- [1..3]; pure (a,b) }
let
и что на самом деле делает<-
. И, наверное, о нотацииdo
. - person lisyarus   schedule 29.02.2016