Пытаюсь понять монады. Оператор

Я новичок в хаскеле и учусь с помощью LearnYouAHaskell. Я просто не могу понять причину оператора (>>).
Реализация по умолчанию:

(>>) :: (Monad m) => m a -> m b -> m b  
m >> n = m >>= \_ -> n 

Что (насколько я понимаю) игнорирует первое значение и возвращает второе. Однако из примера в LearnYouAHaskell получается так:

ghci> Ничего >> Всего 3
Ничего
ghci> Всего 3 >> Ничего
Ничего

Поэтому он не игнорирует первое значение. Тем не менее, после небольшого исследования я нашел эту цитату из здесь

Оператор привязки функции >> игнорирует значение своего первого действия и возвращает в качестве общего результата только результат своего второго действия.

Итак, я озадачен использованием этого оператора и хочу задать два вопроса:

  1. Что он на самом деле делает?
  2. Когда это полезно?

person Tzah Mama    schedule 20.07.2014    source источник


Ответы (5)


Функция >> игнорирует только результат первого значения, но не игнорирует побочный эффект первого значения. Чтобы понять ваш пример, посмотрите, как определяется Maybe Monad:

instance Monad Maybe where
  return = Just
  (Just x) >>= f = f x
  Nothing >>= _ = Nothing

А функция >> определяется так:

m >> k      = m >>= \_ -> k

Nothing >> _ произведет Nothing согласно определению монады Maybe. Во втором примере Just 3 >> Nothing расширяется до Just 3 >>= \_ -> Nothing и производит Nothing. Чтобы дать вам пример того, как игнорируется только значение первого действия, но не игнорируется побочный эффект, рассмотрим следующий пример:

λ> print 3 >> print 4
3
4

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

Функция >> становится полезной, когда вы начинаете использовать другие библиотеки Haskell. Два места, где я иногда использую их, — это работа с парсерами (parsec, attoparsec) и библиотекой Pipes.

person Sibi    schedule 20.07.2014

Он игнорирует значение первого действия, а не само действие.

Just 3 >> Just 5

Значение действия Just 3 равно 3. Он игнорируется в части \_ -> n. Общий результат Just 5.

Just 3 >> Nothing

Значение действия Just 3 равно 3. Он игнорируется в части \_ -> n. Общий результат Nothing.

Nothing >> Just 3

Действие Nothing вообще не производит никакого значения. Что тогда передается правому операнду >>= (или >>)? Это не так! >>= для монады Maybe строится таким образом, что если левое действие равно Nothing, то правое действие вообще не выполняется, а общий результат равен Nothing.

person n. 1.8e9-where's-my-share m.    schedule 20.07.2014

Чтобы завершить ответ Сиби, >> можно увидеть как ; на других языках, таких как C или C++..

Когда вы делаете это на C (или эквиваленте на другом языке)

printf("фу"); printf("бар");

Очевидно, вы печатаете foobar (побочный эффект), но эти вызовы printf также имеют возвращаемое значение, которое в нашем случае представляет собой печатаемую длину, т.е. 3 3. Вы когда-нибудь задумывались, что происходит с этими числами? Их выбрасывают, потому что в C expr 1; выражение 2 означает

  • оценить expr1
  • отказаться от результата
  • оценить expr2

(В этот момент вы могли бы спросить себя, почему компилятор должен беспокоиться об оценке expr1, если он должен отбросить его результат? Из-за побочного эффекта. В случае printf побочным эффектом является печать чего-либо. Вы редко интересуются самим возвращаемым значением.)

Таким образом, ; можно рассматривать как оператор, принимающий 2 выражения и возвращающий новое. Это точно так же, как и оператор >>.

Когда вы пишете

 print "foo" >> print "bar"

он точно эквивалентен printf("foo");printf("bar"), за исключением того, что (и это главное отличие) >> не является чем-то волшебным, как ; в C. >> это определяемый пользователем оператор, и его можно переопределить для каждого типа монады. Вот почему программисты на Haskell так любят Monad: короче говоря, она позволяет вам переопределить собственное поведение ;.

Как мы видели, в C ; просто оценивается выражение и отбрасывается его значение. На самом деле, это немного сложнее, потому что это не так, если это break или return. Ничто в монаде Возможно можно рассматривать как break или return. >> оценивает первое выражение и останавливается, если оно Nothing. В противном случае он отбрасывает свое значение и продолжает работу.

Ваш первый пример можно увидеть в C (я думаю, что это правильно C)

3; return

и

return; 3

В первом примере вычисляется 3, отбрасывается его значение и выполняется возврат. Второй возвращается сразу.

Чтобы ответить на ваш вопрос when is it usefull ? Почти все время, когда вы используете IO, даже если вы редко его видите.

Вместо того, чтобы писать

 print "foo" >> print "bar"

Haskell предоставляет синтаксический сахар, который преобразует (почти) символы новой строки в >> с помощью do-нотации, так что вы напишите

do
  print "foo"
  print "bar"

что строго эквивалентно предыдущей версии (это факт, что версия нотации преобразуется компилятором в предыдущую).

Это даже эквивалентно (хотя и редко используется)

do print "foo"; print "bar"

Подводя итог, >> можно рассматривать как эквивалент ; или новой строки в других языках с той разницей, что ее точное значение зависит от контекста (на который действует монада). >> в монаде Maybe отличается от >> в IO-монаде.

person mb14    schedule 20.07.2014

Прежде всего, давайте проясним вашу путаницу с монадой Maybe. Рассмотрим следующее

instance Monad Maybe where
  return = Just
  (Just x) >>= g = g x
  Nothing  >>= _ = Nothing

Как видите, по определению Nothing >>= _ равно Nothing. Поскольку >> является частным случаем >>=, где параметр игнорируется, результат тот же.

Это связано с тем, что Maybe обычно используются для представления вычислений, которые могут завершиться ошибкой. Это говорит нам, что «однажды потерпев неудачу, ты всегда потерпишь неудачу».

Теперь, чтобы ответить на ваши вопросы.

  1. Цитата, которую вы упомянули, уже отвечает на него.
  2. Это полезно в некоторых ситуациях, когда результат действия просто не нужен. Например, результатом putStrLn является (), что не интересно и не может быть каким-либо образом полезным.
person Evan Sebastian    schedule 20.07.2014

Причина проста: простыми словами для реализации монады нужны две операции:

  • >>=
  • >>

Первый выполняет действие и передает его результат другому действию. Например:

Prelude> :t getLine
getLine :: IO String
Prelude> :t putStrLn
putStrLn :: String -> IO ()

Две функции: первая getLine просто возвращает строку, завернутую в IO, она читает строку из stdin и переносит ее в IO. Второй putStrLn получает String и печатает его. Привязка имеет следующий тип:

:t (>>=)
(>>=) :: Monad m => m a -> (a -> m b) -> m b

Его можно представить как IO String -> (String -> IO String) -> IO String, поэтому вы можете объединить эти две или более функций с (>>=), выполнив getLine и передав результат String в putStrLn:

getLine >>= putStrLn

Таким образом, вы можете объединить эти две или более функций в одно действие.

Второй >> производит почти то же самое, но в результате предыдущего действия он не нужен.

:t (>>)
(>>) :: Monad m => m a -> m b -> m b

Он просто выполняет первое действие, а затем второе, и второе действие не нужно в результате первого действия:

putStrLn "Hello" >> putStrLn "World"
person 0xAX    schedule 20.07.2014