Обязательно ли используется нотация в контексте монады?

В отчете Haskell за 2010 г. говорится

A do expression provides a more conventional syntax for monadic programming. It allows an expression such as

putStr "x: " >>
getLine >>= \l ->
return (words l)

to be written in a more traditional way as:

do putStr "x: "
   l <- getLine
   return (words l)

Haskell the Craft of Functional Programming от Томпсона говорит

We'll continue to use the do notation, but will keep in mind that it essentially boils down to the existence of a function (>>=) which does the work of sequencing I/O programs, and binding their results for future use.

Означает ли вышеизложенное, что нотация do обязательно используется в контексте монады?

Если да, то почему следующий функтор использует нотацию do?

instance    Functor IO  where
    --  fmap    ::  (a  ->  b)  ->  IO  a   ->  IO  b
    fmap    g   mx  =   do  {x  <-  mx; return  (g  x)}

Да. do нотация, как уже упоминалось, — это просто синтаксический сахар для монадных операций. В вашем примере с экземпляром Functor для IO мы уже определили монадные операции над IO и используем их для определения fmap.

AJF 25.07.2019 01:53

Ну, есть также ApplicativeDo, расширение GHC, которое позволяет использовать нотацию do с аппликативами. Я никогда не пытался использовать его сам, но я уверен, что где-то читал, что он может быть официально добавлен в язык всякий раз, когда они готовят следующий отчет.

Robin Zigmond 25.07.2019 01:58

@RobinZigmond, надеюсь, что нет. Это не самое лучшее из расширений. Но, возможно, его можно очистить.

dfeuer 25.07.2019 05:55
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
3
358
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Да. Как говорится в цитируемой статье, do-обозначение простосинтаксический сахардля монадных операций.

Это правила для десахаризации do-нотации:

  1. do {foobar; ...} = foobar >> do {...} (он же foobar >>= \_ -> do {...})
  2. do {a <- foobar; ...} = foobar >>= \a -> do {...}
  3. do {foobar} = foobar

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

Так, например, как говорится в статье, do {putStr "x: "; l <- getLine; return (words l)} в точности равно putStr "x: " >> (getLine >>= \l -> return (words l)), что вы можете подтвердить с помощью правил удаления сахара.

В приведенном выше определении для Functor IO экземпляр Monad IO уже определен, поэтому мы также используем его для определения экземпляра Functor.

Также может быть полезно отметить, что все монады обязательно являются функторами (см. определение класса типов Monad), поэтому, когда кто-то говорит, что do-нотация работает с монадами, она также обязательно работает и с функторами. Я подозреваю, что это может быть пунктом путаницы.


Стоит отметить, что в некоторых ограниченных случаях можно использовать только Applicative операции, а не более общие Monad операции. Например, пример, приведенный в статье, может быть записан как putStr "x: " *> (pure words <*> getLine). Существует экспериментальное расширение языка под названием ApplicativeDo, которое добавляет в GHC возможность распознавать такие ситуации и обобщать определенные случаи do-нотации на все аппликативы, а не только на все монады.

Спасибо. Откуда вы знаете, что «в определении Functor IO, которое вы цитируете выше, экземпляр Monad IO уже определен», т.е. что определено перед чем?

Tim 25.07.2019 02:15

@ Тим, я знаю это целостно, потому что они используют do-нотацию для определения fmap! Однако, как правило, в Haskell невозможно измерить порядок определения, поскольку возможна взаимная рекурсия. Скорее, я имею в виду, что экземпляр Monad определен и не относится к fmap рекурсивно. Я могу узнать это, посмотрев определение на гугл.

AJF 25.07.2019 02:18

Посмотрите на правило 3 немного внимательнее. Это чисто синтаксическое обезуглероживание. do () является действительным, а не монадическим. Это просто не очень полезно.

Carl 25.07.2019 02:35

@Carl Спасибо, что указали на этот пограничный случай. Я добавил немного об этом. На самом деле мне пришлось проверить с помощью GHCI, что do () действительно действителен!

AJF 25.07.2019 02:41

@AJFarmar Кроме того, если вы делаете do { one_expr }, это тоже не монадично. Это может показаться крайним случаем, но я видел предложение использовать его, когда $ не совсем правильно, например. делаю (,) <$> do long_exp_number_1 {-newline-} <*> do long_exp_number_2. Я дам ссылку на него, если найду оригинальный совет.

bradrn 25.07.2019 02:52

Что ж, с расширением Синтаксис Rebindable он будет использовать любые (>>=), (>>) и fail, которые находятся в области видимости. Так что технически Monad в таком случае не требуется, и вы можете сделать что-то глупое, например:

{-# LANGUAGE RebindableSyntax #-}

a >>= b = b a
return a = a

-- test == ("foo","boo")
test = do
  a <- "foo"
  b <- "boo"
  return (a,b)

Другие вопросы по теме