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

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

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

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

6 ответов

Решение

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

То есть что-то, что имеет тенденцию быть довольно глобальным, например, журнал или конфигурация, вы бы предложили добавить в монаду 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

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

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

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

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

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

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

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

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