Кто-нибудь когда-нибудь сталкивался с Трансформатором Монад в дикой природе?

В моей сфере деятельности - ИТ-отделы бэк-офиса для финансового учреждения - очень часто программный компонент имеет глобальную конфигурацию, регистрирует свой прогресс, имеет какое-то короткое замыкание обработки ошибок / вычислений ... можно хорошо смоделировать с помощью монад Reader, Writer, Maybe и т.п. в Haskell и составить вместе с преобразователями монад.

Но, похоже, есть некоторые недостатки: концепция, лежащая в основе преобразователей монад, довольно сложна и трудна для понимания, преобразователи монад приводят к очень сложным сигнатурам типов и приводят к некоторому снижению производительности.

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


person martingw    schedule 03.05.2010    source источник
comment
По крайней мере, сигнатуры типов во многих случаях можно легко улучшить, заключив стек преобразователя монад в newtype и автоматически создав любые нужные вам экземпляры.   -  person C. A. McCann    schedule 03.05.2010
comment
@camccann: Вы сделали это с трансформаторами MTL? Мне никогда не приходилось работать с автоматическим выводом (через GeneralizedNewtypeDeriving). Я придерживался псевдонимов простого типа для своего приложения, что не так уж плохо, но не идеально.   -  person jrockway    schedule 04.05.2010
comment
Да, автоматическое определение, к сожалению, не работает, если тип не является линейным по своим переменным: newtype Foo r a = Foo (ReaderT r (State r) a) deriving (Functor, Monad) не будет работать, потому что он упоминает r дважды.   -  person luqui    schedule 04.05.2010
comment
@jrockway: У меня он отлично работал с относительно простыми стеками. Обычно я создавал собственный стек с IO в основе, который служил бы ядром приложения, поэтому я избегал проблемы, упомянутой Луки, просто задавая параметры типа, а не параметризуя newtype. Обычно стеки заканчивались Reader, Error, каким-то Random или Supply и немного State, чего бы это ни стоило.   -  person C. A. McCann    schedule 04.05.2010
comment
Пожалуйста, покажите код. Я никогда не видел, чтобы MonadError успешно автоматически производился.   -  person jrockway    schedule 05.05.2010
comment
Я пробовал: newtype Foo a = Foo (ErrorT String IO a) deriving (Monad, MonadError). Монада получена нормально, MonadError ... нет. (GHC 6.12.1 / Debian x64)   -  person jrockway    schedule 05.05.2010
comment
@jrockway: попробуйте вместо этого newtype Foo a = Foo (ErrorT String IO a) deriving (Monad, MonadError String), поскольку сообщение об ошибке GHC косвенно предполагает ('MonadError' не имеет арности 1).   -  person C. A. McCann    schedule 06.05.2010
comment
@camccann: Ага, отлично. Спасибо!   -  person jrockway    schedule 06.05.2010
comment
Это может быть вам интересно: speakerdeck.com/u/jrwest/p/monad-transformers   -  person ron    schedule 28.07.2012
comment
Несмотря на то, что я понимаю концепцию преобразователя монад, я, по-видимому, не могу понять, как работает стек преобразователя. Т.е. Я никогда не уверен, есть ли у вас m n a, какое из n или m находится вверху или внизу стека и что это означает. Поэтому я избегаю преобразователя монад, но это только я.   -  person mb14    schedule 04.09.2016


Ответы (6)


Сообщество Haskell разделились по этому поводу.

  • Джон Хьюз сообщает, что ему легче обучать монадным преобразователям, чем монадам, и что его ученики лучше справляются с подходом «сначала преобразователи».

  • Разработчики GHC обычно избегают монадных преобразователей, предпочитая сворачивать свои собственные монады, которые объединяют все необходимые им функции. (Мне только сегодня недвусмысленно сказали, что GHC не будет использовать преобразователь монад, который я определил три дня назад.)

Для меня преобразователи монад во многом похожи на программирование без точек (т.е. программирование без именованных переменных), что имеет смысл; в конце концов, они как раз беспроблемное программирование на уровне типов. Мне никогда не нравилось программирование без точек, потому что полезно иметь возможность вводить случайное имя.

То, что я наблюдаю на практике,

  • Количество преобразователей монад, доступных на Hackage, очень велико, и большинство из них довольно простые. Это классический пример проблемы, когда сложнее изучить большую библиотеку, чем катать собственные экземпляры.

  • Такие монады, как Writer, State и Environment, настолько просты, что я не вижу особой пользы от преобразователей монад.

  • Превосходство монадных трансформаторов - в модульности и многократном использовании. Это свойство прекрасно продемонстрировали Лян, Худак и Джонс в их знаменитой статье "Monad Трансформаторы и модульные интерпретаторы ».

Являются ли монадные преобразователи лучшей практикой при решении этих общих задач, упомянутых выше?

Я бы сказал нет. Преобразователи монад лучше всего там, где у вас есть линейка продуктов связанных абстракций, которые вы можете создать, составив и повторно используя преобразователи монад по-разному. В таком случае вы, вероятно, разработаете ряд преобразователей монад, которые важны для вашей проблемной области (например, тот, который был отклонен для GHC), и вы (а) составите их несколькими способами; (б) добиться значительного повторного использования большинства трансформаторов; (c) инкапсулируют что-то нетривиальное в каждый преобразователь монад.

Мой преобразователь монад, который был отклонен для GHC, не отвечал ни одному из критериев (a) / (b) / (c) выше.

person Norman Ramsey    schedule 03.05.2010
comment
Спасибо, хороший пост! Я действительно поражен тем, что кто-то находит, что монадные трансформеры легче преподавать, чем монады! У тебя под рукой есть слайды? Интересно, как он это делает! - person martingw; 04.05.2010
comment
Я более искренне поражен тем, что, по-видимому, Норман Рэмси каким-то образом находит время, чтобы давать частые, подробные ответы о переполнении стека, фактически писать нетривиальный код на Haskell, при этом не отставая от любых требований своей повседневной работы ... когда-нибудь спал? - person C. A. McCann; 04.05.2010
comment
@camcann: Спасибо, это есть на моей веб-странице. На самом деле, многие считают меня консорциумом: spike.livejournal.com/468239 .html? thread = 2204687 # t2204687 - person Norman Ramsey; 05.05.2010
comment
Очень интересный ответ, не могли бы вы или кто-то еще прокомментировать, как 3 пункта (а) складываются по-разному; (б) добиться значительного повторного использования большинства трансформаторов; (c) инкапсулируют что-то нетривиальное в каждый преобразователь монад. как бы выглядело на практике? Был бы прекрасен простой и легкий для понимания пример. Например, что означало бы их составление по-разному на практике? - person jhegedus; 14.09.2015

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

Думаю, это немного преувеличение:

  • Использовать конкретный стек монад преобразователя не сложнее, чем простую монаду. Просто подумайте о слоях \ стеках, и все будет в порядке. Вам почти всегда не нужно поднимать чистую функцию (или конкретное действие ввода-вывода) более одного раза.
  • Как уже упоминалось, скройте свой стек Monad в новом типе, используйте обобщенное наследование и скройте конструктор данных в модуле.
  • Старайтесь не использовать конкретный стек Monad в сигнатуре типа функции, напишите общий код с классами типа Monad, такими как MonadIO, MonadReader и MonadState (используйте расширение гибких контекстов, которое стандартизировано в Haskell 2010).
  • Используйте библиотеки, такие как fclabels, для уменьшения шаблонных действий, которые обращаются к частям записи в монаде.

Преобразователи монад - не единственные варианты, вы можете написать собственную монаду, используя монаду-продолжение. У вас есть изменяемые ссылки / массивы в IO (глобальный), ST (локальный и управляемый, без действий IO), MVar (синхронизация), TVar (транзакционный).

Я слышал, что потенциальные проблемы эффективности с преобразователями Monad можно уменьшить, просто добавив прагмы INLINE для связывания / возврата в исходный код библиотеки mtl / transformers.

person snk_kid    schedule 03.05.2010
comment
@snk_kid: Спасибо за ваше мнение! Итак, что-то, что имеет тенденцию быть довольно глобальным, например, журнал или конфигурацию, вы бы предложили поместить в монаду ввода-вывода? Глядя на (правда, очень ограниченный набор) примеров, я прихожу к мысли, что код Haskell имеет тенденцию быть либо чистым (то есть вовсе не монадическим), либо монадой ввода-вывода. Или это заблуждение? - person martingw; 04.05.2010

Когда я изучал монады, я создал приложение, используя стек StateT ContT IO, чтобы создать библиотеку моделирования дискретных событий; продолжения использовались для хранения монадических потоков, при этом StateT содержал очередь выполняемых потоков, а другие очереди использовались для приостановленных потоков, ожидающих различных событий. Это сработало довольно хорошо. Я не мог понять, как написать экземпляр Monad для оболочки newtype, поэтому я просто сделал его синонимом типа, и это сработало очень хорошо.

В наши дни я, наверное, скатал бы свою монаду с нуля. Однако всякий раз, когда я это делаю, я обнаруживаю, что смотрю на «Все о монадах» и источник MTL, чтобы напомнить мне, как выглядят операции связывания, так что в некотором смысле я все еще думаю в терминах стека MTL, хотя результат это кастомная монада.

person Paul Johnson    schedule 04.05.2010

Недавно я «упал» на композицию монад в контексте F #. Я написал DSL, сильно полагаясь на монаду состояния: все компоненты полагаются на монаду состояния: синтаксический анализатор (монада синтаксического анализатора на основе монады состояния), таблицы сопоставления переменных (более одной для внутренних типов), таблицы поиска идентификаторов. И поскольку все эти компоненты работают вместе, они полагаются на одну и ту же монаду состояний. Поэтому существует понятие композиции состояний, которое объединяет различные локальные состояния, и понятие средств доступа к состоянию, которые придают каждому алгоритму свою собственную видимость состояния.

Изначально дизайн действительно был «всего лишь одной большой государственной монадой». Но затем мне стали нужны состояния только с локальным временем жизни, но все еще в контексте «постоянного» состояния (и опять же, все эти состояния управляются монадами состояний). Для этого мне действительно нужно было ввести преобразователи монад состояния, которые увеличивают состояние и вместе адаптируют монады состояния. Я также добавил преобразователь для свободного перемещения между монадой состояния и монадой состояния продолжения, но я не стал его использовать.

Поэтому отвечу на вопрос: да, трансформеры монад существуют в «дикой природе». Тем не менее, я категорически против использования их «из коробки». Напишите свое приложение с помощью простых строительных блоков, используя небольшие мосты ручной работы между модулями. Если вы все же в конечном итоге используете что-то вроде преобразователя монад, это здорово; Не начинайте оттуда.

И о типовых сигнатурах: я стал думать об этом типе программирования как о чем-то очень похожем на игру в шахматы с завязанными глазами (и я не шахматист): ваш уровень навыков должен быть таким, чтобы вы "видели" свои функции и типы, подходящие друг к другу. Сигнатуры типов в конечном итоге отвлекают, если только вы явно не хотите добавить ограничения типа по соображениям безопасности (или потому, что компилятор вынуждает вас указать их, например, с записями F #).

person user519985    schedule 07.12.2010

Я считаю это заблуждением, только монада IO не чистая. монады наподобие Write / T / Reader / T / State / T / ST все еще остаются чисто функциональными.

Мне кажется, что существует несколько понятий относительно термина «чистый / нечистый». Ваше определение «IO = unpure, все остальное = чисто» звучит аналогично тому, о чем говорит Пейтон-Джонс в «Эффектах приручения» (http://ulf.wiger.net/weblog/2008/02/29/peyton-jones-taming-effects-the-next-big-challenge/). С другой стороны, Haskell реального мира (на последних страницах главы о преобразователе монад) противопоставляет чистые функции монадическим функциям в целом, утверждая, что вам нужны разные библиотеки для обоих миров. Кстати, можно утверждать, что ввод-вывод также является чистым, его побочные эффекты инкапсулируются в функцию состояния с типом RealWorld -> (a, RealWorld). В конце концов, Haskell называет себя чисто функциональным языком (включая ввод-вывод, я полагаю :-).)

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

person martingw    schedule 04.05.2010
comment
unsafePerformIO не является чистым (надеюсь, у меня правильное название). По сути, это IO a - ›a. - person Anonymous; 12.09.2011

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

Я считаю это заблуждением, только монада IO не чистая. монады наподобие Write / T / Reader / T / State / T / ST все еще остаются чисто функциональными. Вы можете написать чистую функцию, которая использует любую из этих монад внутри, как в этом совершенно бесполезном примере:

foo :: Int -> Int
foo seed = flip execState seed $ do
    modify $ (+) 3
    modify $ (+) 4
    modify $ (-) 2

Все, что это делает, - это неявно распределять / связывать состояние, то, что вы бы сделали вручную явно, нотация do здесь просто дает вам приятный синтаксический сахар, чтобы он выглядел императивным. Здесь вы не можете выполнять какие-либо действия ввода-вывода, вы не можете вызывать какие-либо внешние функции. Монада ST позволяет вам иметь реальные изменяемые ссылки в локальной области видимости, имея при этом чистый функциональный интерфейс, и вы не можете выполнять какие-либо действия ввода-вывода в нем, он все еще остается чисто функциональным.

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

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

Вы видели главу из Real World Haskell, в которой используются преобразователи монад ?

person snk_kid    schedule 03.05.2010