it-roy-ru.com

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

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

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

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

52
martingw

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

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

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

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

На практике я наблюдаю

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

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

  • Где сияют монадные трансформаторы, так это модульность и повторное использование. Это свойство прекрасно продемонстрировано Ляном, Худаком и Джонсом в их знаковой статье "Монадные трансформаторы и модульные переводчики" .

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

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

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

43
Norman Ramsey

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

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

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

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

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

8
snk_kid

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

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

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

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

3
user519985

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

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

2
Paul Johnson

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

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

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

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

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

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

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

2
snk_kid

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

Мне кажется, что существует более одного понятия о термине чистый/не чистый. Ваше определение "IO = unpure, все остальное = pure" звучит похоже на то, о чем говорит Пейтон-Джонс в "Приручающих эффектах" ( http://ulf.wiger.net/weblog/2008/02/29/peyton -jones приучение эффектов, заместитель следующего большого вызова / ). С другой стороны, Real World Haskell (на последних страницах главы Monad Transformer) сравнивает чистые функции с монадической функцией в целом, утверждая, что вам нужны разные библиотеки для обоих миров. Кстати, можно утверждать, что IO также чист, его побочные эффекты заключаются в функцию State с типом RealWorld -> (a, RealWorld) . В конце концов, Haskell называет себя чисто функциональным языком (включая IO, я полагаю :-).)

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

2
martingw