2013-05-09 13 views
21

Как вы разрабатываете и создаете свои монадические стопки? Впервые мне нужно построить монодичный стек (используя трансформаторы), чтобы решить проблему реального мира, но я не совсем уверен, в каком заказывал для укладки трансформаторов. Как вы уже знаете, до тех пор, как вычисление имеет вид * -> *, в основном все, что может играть роль внутренней монады в трансформаторе, таким образом, несколько вопросов:Как создать монодичный стек?

  • Если какой-то конкретный трансформатор будет в верхней части стек (например, ReaderT? WriterT?)
  • Что должно вести дизайн? Интуиция? Типы? (например, сформируйте стек в соответствии с потребностями вашего API)
  • Является ли каждый стек изоморфным друг другу (в определенной степени), или, может быть, если я создам свой стек неправильно, я мог бы оказаться неспособным использовать определенные лежащие в основе монады или иметь большой раздутый беспорядок lift . lift . liftIO [...]? Мое ощущение кишки подсказывает, что если трансформаторы получают некоторые экземпляры (например, MonadReader, MonadIO и т. Д., Как и большинство трансформаторов в mtl do), не имеет значения, в каком порядке я помещал трансформаторы.

Я заинтересован в слушании от опытных Haskellers о лучших практиках или эмпирических правилах.

forever $ print "Thanks!"

А.

ответ

21

Требуется опыт. Следует помнить, что трансформатор монады ничего не знает о монаде, которое он трансформирует, поэтому внешняя «привязана» к поведению внутреннего. Так

StateT s (ListT m) a 

, в первую очередь, недетерминирован вычисление из внутренней монады. Тогда, принимая недетерминизм как нормальный, вы добавляете состояние - то есть каждая «ветвь» недетерминизма будет иметь свое собственное состояние.

Контраст с ListT (StateT s m) a, что в первую очередь с сохранением состояния - т.е. там будет только одно состояния для всего вычисления (по модулю m), и вычисление будет действовать «однопоточное» в состоянии, потому что именно State средства. Недетерминизм будет выше этого - так что ветви смогут наблюдать изменения состояния предыдущих неудавшихся ветвей. (В этой конкретной комбинации это действительно странно, и я никогда не нуждался в ней).

Вот diagram Дэн Piponi, который дает некоторую полезную интуицию:

monad doodles

Я также считаю, что полезно расширить к типу реализации, чтобы дать мне почувствовать, какие вычисления это , ListT трудно расширить, но вы можете увидеть его как «неопределенный», а StateT легко расширить. Поэтому для приведенного выше примера я бы посмотрел

StateT s (ListT m) a =~ s -> ListT m (a,s) 

I.e. он принимает входящее состояние и возвращает много исходящих состояний. Это дает вам представление о том, как это будет работать. Аналогичный подход заключается в том, чтобы посмотреть тип функции run, который вам понадобится для вашего стека, - соответствует ли это вашей информации и необходимой информации?

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

ReaderT, WriterT и StateT являются наиболее распространенными трансформаторами. Во-первых, все они коммутируют друг с другом, поэтому не имеет значения, какой заказ вы их вложите (рассмотрите, используя RWS, если вы используете все три). Кроме того, на практике я обычно хочу, чтобы они были снаружи, с более богатыми трансформаторами, такими как ListT, LogicT и ContT с внутренней стороны.

ErrorT и MaybeT обычно проходят снаружи вышеуказанных трех; давайте посмотрим на то, как MaybeT взаимодействует с StateT:

MaybeT (StateT s m) a =~ StateT s m (Maybe a) =~ s -> m (Maybe a, s) 
StateT s (MaybeT m) a =~ s -> MaybeT m (a,s) =~ s -> m (Maybe (a,s)) 

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

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

12

Это довольно широкий вопрос. Я просто дам вам некоторые основные идеи для работы.

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

Важно отметить, что порядок ваших монадных трансформаторов фактически контролирует их семантику. Мой любимый пример объединяет что-то вроде ListT ¹ с EitherT для обработки ошибок. Если у вас есть ListT снаружи, то всего расчет может завершиться с ошибкой. Если у вас есть EitherT снаружи, то каждая ветка может выйти из строя отдельно. Таким образом, вы можете фактически контролировать, как ошибки взаимодействуют с недетерминированностью, просто изменяя порядок ваших трансформаторов!

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

¹: ListT от Control.Monad.Trans есть некоторые проблемы, поэтому предположим, что это ListT done right.

Смежные вопросы