Попытка понять Монады. Оператор >>

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

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

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

ghci > Ничего → Просто 3
Ничего
ghci > Просто 3 → Ничего Ничего

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

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

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

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

Ответ 1

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

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

И >> функция определяется следующим образом:

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

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

λ> print 3 >> print 4
3
4

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

Функция

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

Ответ 2

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

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.

Ответ 3

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

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

Е ( "Foo" );  Е ( "бар" );

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

  • оценить expr1
  • вывести его результат
  • оценить expr2

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

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

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

 print "foo" >> print "bar"

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

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

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

3; return

и

return; 3

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

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

Вместо записи

 print "foo" >> print "bar"

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

do
  print "foo"
  print "bar"

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

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

do print "foo"; print "bar"

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

  • >>=
  • >>

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

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"