В чем разница между блоками ‹- и let внутри?

Я не понимаю, когда я должен использовать let и когда я должен использовать привязку <-.


person E.F    schedule 29.02.2016    source источник
comment
Узнайте больше о том, что на самом деле означает let и что на самом деле делает <-. И, наверное, о нотации do.   -  person lisyarus    schedule 29.02.2016


Ответы (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 для присвоения имен всему остальному.

person Matthew Walton    schedule 29.02.2016

<- соответствует >>= (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.
person muhuk    schedule 29.02.2016

Хороший пример для визуализации того, что делает <-:

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) }
person Ingo    schedule 29.02.2016