Подтвердить что ты не робот

"<-" привязки в обозначении

Мне трудно это понять. При написании в обозначении, как выглядят следующие две строки?

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

Я не вижу этого. Иногда один работает, несколько раз другой. Но редко и то, и другое. "Узнайте, что вы haskell" говорит, что <- связывает правую сторону с символом слева. Но как это отличается от простого определения x с помощью let?

4b9b3361

Ответ 1

Оператор <- будет извлекать значение из монады, а инструкция 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" оценивается, но полученное монадическое действие не выполняется.

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

Ответ 2

В привязке let выражение может иметь любой тип, и все, что вы делаете, дает ему имя (или сопоставление шаблонов по его внутренней структуре).

В версии <- выражение должно иметь тип m a, где m - это любая монада, в которой находится блок do. Поэтому в монаде IO, например, привязки этой формы должны имеют некоторое значение типа IO a в правой части. Часть a (внутри монадического значения) - это то, что связано с рисунком с левой стороны. Это позволяет извлекать "содержимое" монады в пределах ограниченного объема блока do.

Обозначение do - это, как вы могли прочитать, только синтаксический сахар над монадическими операторами привязки (>>= и >>). x <- expression де-сахара до expression >>= \x -> и expression (само по себе, без <-) де-сахара до expression >>. Это просто дает более удобный синтаксис для определения длинных цепочек монадических вычислений, которые в противном случае имеют тенденцию создавать довольно внушительную массу вложенных лямбдов.

let привязки вообще не де сахара. Единственное различие между let в блоке do и let вне блока do заключается в том, что для версии do не требуется ключевое слово in; имена, которые он связывает, неявно входят в область остальной части блока do.

Ответ 3

В форме let expression является немонодическим значением, а правая часть a <- является монадическим выражением. Например, вы можете иметь операцию ввода-вывода (типа IO t) во втором типе привязки. В деталях две формы можно грубо перевести как (где ==> показывает перевод):

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

и

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

Ответ 4

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 s:

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

Теперь IO совсем не отличается. Это контейнер для одного значения, но это "опасная" нечистая ценность, как вирус, которую мы не должны касаться напрямую. do -Block - это наша стеклянная оболочка, а <- - встроенные "перчатки" для управления вещами внутри. С return мы поставляем полный, неповрежденный контейнер (а не только опасный контент), когда мы будем готовы. Кстати, функция add работает со значениями IO (которые мы получили из файла или командной строки или случайного генератора...).

Ответ 5

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

Ответ 6

Вот простой пример, показывающий вам разницу. Рассмотрим два следующих простых выражения:

letExpression = 2
bindExpression = Just 2

Информация, которую вы пытаетесь получить, - это номер 2. Вот как вы это делаете:

let x = letExpression
x <- bindExpression

let прямо ставит значение 2 в x. <- извлекает значение 2 из Just и помещает его в x.

В этом примере вы можете видеть, почему эти две записи не взаимозаменяемы:

let x = bindExpression будет непосредственно помещать значение Just 2 в x. x <- letExpression нечего было бы извлечь и положить в x.