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

Как вы определяете монадические шаблоны проектирования?

Я пытаюсь научиться Haskell. Я начинаю понимать концепцию монады и начинаю использовать известные монад в моем коде, но у меня все еще возникают трудности с приближением монад с дизайнерской точки зрения. В OO существует несколько правил, таких как "идентифицировать существительные" для объектов, наблюдать за каким-то состоянием и интерфейсом... но я не могу найти эквивалентные ресурсы для монад.

Итак, как вы определяете проблему как монадическую по своей природе? Каковы хорошие шаблоны проектирования для монадического дизайна? Какой ваш подход, когда вы понимаете, что какой-то код будет лучше реорганизован в монаду?

4b9b3361

Ответ 1

Полезное эмпирическое правило - когда вы видите значения в контексте; монады можно рассматривать как слоистые "эффекты" на:

  • Может быть: partity (использует: вычисления, которые могут выйти из строя)
  • Либо: ошибки короткого замыкания (использование: обработка ошибок/исключений)
  • [] (монада списка): недетерминированность (использует: создание списка, фильтрацию,...)
  • Состояние: единственная изменяемая ссылка (использует: состояние)
  • Читатель: общая среда (использует: привязки переменных, общую информацию,...)
  • Writer: вывод или накопление "бокового канала" (использует: ведение журнала, ведение счетчика только для записи...)
  • Cont: нелокальный поток управления (использует: слишком много для отображения)

Обычно вы обычно должны проектировать свою монаду путем разбиения на монадные трансформаторы из стандартной Monad Transformer Library, которая позволяет объединить выше эффекты в одну монаду. Вместе они обрабатывают большинство монад, которые вы, возможно, захотите использовать. В MTL есть несколько дополнительных монадов, таких как вероятность и supply monads.

Что касается разработки интуиции относительно того, является ли вновь определенный тип монадой и как она ведет себя как одна, вы можете думать об этом, переходя от Functor в Monad:

  • Функтор позволяет преобразовывать значения с помощью чистых функций.
  • Аппликативный позволяет вставлять чистые значения и выражать приложение - (<*>) позволяет перейти от встроенной функции и встроенного аргумента во встроенный результат.
  • Monad позволяет структуре встроенных вычислений зависеть от значений предыдущих вычислений.

Самый простой способ понять это - посмотреть на тип join:

join :: (Monad m) => m (m a) -> m a

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

Интересно, что это может быть слабостью структурирования вещей монадически: с Applicative структура вычисления статична (т.е. заданное вычисление Applicative имеет определенную структуру эффектов, которые не могут изменяться на основе промежуточных значений), тогда как при Monad он является динамическим. Это может ограничить оптимизацию, которую вы можете сделать; например, аппликативные парсеры менее мощные, чем монадические (ну, это не строго верно, но это эффективно), но они могут оптимизироваться лучше.

Обратите внимание, что (>>=) можно определить как

m >>= f = join (fmap f m)

и поэтому монада может быть определена просто с помощью return и join (предполагая, что она Functor; все монады являются аппликативными функторами, но иерархия классов Haskell, к сожалению, не требует этого для исторические причины).

В качестве дополнительной заметки вы, вероятно, не должны слишком сильно фокусироваться на монадах, независимо от того, какой шум они получают от ошибочных не-Haskellers. Есть много типов, которые представляют значимые и мощные шаблоны, и не все лучше всего выражается как монада. Applicative, Monoid, Foldable... эта абстракция для использования полностью зависит от вашей ситуации. И, конечно, только потому, что что-то монада не означает, что это не может быть и другое; будучи монадой, является еще одним свойством типа.

Итак, вы не должны слишком много думать о "идентификации монад"; вопросы больше похожи:

  • Может ли этот код выражаться в более простой монадической форме? С чем монада?
  • Этот тип я только что определил монаду? Какие общие шаблоны, закодированные стандартными функциями на монадах, можно использовать?

Ответ 2

Следуйте за типами.

Если вы обнаружите, что у вас есть письменные функции со всеми этими типами

  • (a -> b) -> YourType a -> YourType b
  • a -> YourType a
  • YourType (YourType a) -> YourType a

или все эти типы

  • a -> YourType a
  • YourType a -> (a -> YourType b) -> YourType b

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

(Помните, что вы можете изменить порядок аргументов, поэтому, например, YourType a -> (a -> b) -> YourType b отображается только (a -> b) -> YourType a -> YourType b.)

Не смотрите только на монады! Если у вас есть функции всех этих типов

  • YourType
  • YourType -> YourType -> YourType

и они подчиняются моноидным законам, у вас есть моноид! Это тоже ценно. Аналогично для других типов, наиболее важно Functor.

Ответ 3

Здесь отображается эффект monads:

  • Возможно - короткое замыкание на частичность/отказ
  • Либо - отчет об ошибках/короткое замыкание (например, возможно, с дополнительной информацией)
  • Writer - записывать только "состояние", обычно регистрируясь
  • Считыватель - состояние только для чтения, обычная передача среды
  • Состояние - состояние чтения/записи
  • Возобновление - pausable вычисление
  • Список - многократные успехи

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

Важно отметить, что монадов мало. Есть некоторые экзотические, которые не входят в стандартные библиотеки, например, монады вероятности и вариации монады Cont, такие как Codensity. Но если вы не сделаете что-то математическое, вряд ли вы придумаете (или обнаружите) новую монаду, однако, если вы используете Haskell достаточно долго, вы построите много монадов, которые являются разными комбинациями стандартных.

Изменить. Также обратите внимание, что порядок, в котором вы складываете монадные трансформаторы, приводит к разным монадам:

Если вы добавите ErrorT (трансформатор) в монадию Writer, вы получите эту монаду Either err (log,a) - вы можете получить доступ только к журналу, если у вас нет ошибки.

Если вы добавите WriterT (трансфомер) в Monad Error, вы получите эту монаду (log, Either err a), которая всегда дает доступ к журналу.

Ответ 4

Это своего рода отказ от ответа, но я чувствую, что в любом случае важно сказать. Просто спросите! StackOverflow,/r/haskell и канал #haskell irc - отличные места для быстрой обратной связи от умных людей. Если вы работаете над проблемой, и вы подозреваете, что есть какая-то монашеская магия, которая может облегчить ее, просто спросите! Сообщество Haskell любит решать проблемы и смехотворно дружит.

Не поймите неправильно, я не поощряю вас никогда не учиться для себя. Напротив, взаимодействие с сообществом Haskell - один из лучших способов узнать. LYAH и RWH, 2 книги Haskell, которые свободно доступны в Интернете, также настоятельно рекомендуется.

О, и не забывайте играть, играть, играть! По мере того, как вы играете с монадическим кодом, вы начнете ощущать, какие "формы" имеют монады, и когда монадические комбинаторы могут быть полезны. Если вы переворачиваете свою собственную монаду, тогда обычно система типов будет направлять вас к очевидному, простому решению. Но, честно говоря, вам редко приходится катить свой экземпляр Monad, поскольку библиотеки Haskell предоставляют массу полезных вещей, как упоминалось другими респондентами.