Является ли/должно ли считаться плохой практикой перенос функций в монадный преобразователь?

Допустим, мы хотим использовать ReaderT [(a,b)] вместо монады Maybe, а затем мы хотим выполнить поиск в списке.

Теперь простой и не слишком необычный способ:

первая возможность

find a = ReaderT (lookup a)

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

второй вариант

find a = do  y <- ask 
             lift (lookup a y)

Аналогичные идеи применимы для упаковки MaybeT, StateT, State и Reader. Обычно я пишу что-то вроде первого примера, но в большинстве случаев действительно очевидно, как написать это как второй пример, и вы даже можете сказать, что это более читабельно. Итак, мой вопрос: следует ли считать код, подобный первому примеру, плохим?


person HaskellElephant    schedule 30.11.2010    source источник
comment
Также можно написать find a = lift . lookup a =<< ask, что так же понятно (ИМХО), как и второй вариант, но короче.   -  person Antal Spector-Zabusky    schedule 30.11.2010
comment
или используйте fmap: find a = fmap (поиск a) ask = поиск a ‹$› ask   -  person urso    schedule 01.12.2010
comment
Хреново, что перестали экспортировать конструктор   -  person fuz    schedule 04.12.2010
comment
@FUZxxl, они не перестали экспортировать его, как говорится, однако больше нет newtype State s a = State {runState :: s -> (a, s)} (версия 1.1.1), а скорее type State s = StateT s Identity (версия 2.0.0). Я не вижу ничего плохого в изменении API таким образом, и на самом деле все могло бы измениться, чтобы сломать любой хорошо написанный код, планирование такой совместимости заранее кажется глупым, но я действительно думаю, что конструкторы меняются с большей вероятностью, чем ask . Почти бессмысленно, но все же актуально =D.   -  person HaskellElephant    schedule 04.12.2010
comment
Насколько правильно. Мне действительно это не нравится. В этом случае они должны, по крайней мере, в некоторых RULES прагмах превратить код, подобный приведенному выше, в более приятный способ.   -  person fuz    schedule 04.12.2010


Ответы (3)


Текущая версия библиотеки mtl, основанная на библиотеке transformers, экспортирует функцию reader :: (r -> a) -> Reader r a именно для этой цели при использовании простой монады Reader. Таким образом, мы видим, что дизайн библиотеки учитывает это использование. Поскольку для ReaderT такой функции не предусмотрено, мы можем с некоторой уверенностью сказать, что официально поддерживаемый способ сделать это с ReaderT — напрямую использовать конструктор.

Я соглашусь с вами, если вы скажете, что аналог readerT :: Monad m => (r -> a) -> ReaderT r m a надо добавить в библиотеку. Это было бы хорошо как для согласованности, так и для возможности когда-нибудь изменить внутреннее представление без нарушения чьего-либо кода.

Но пока ваш «первый вариант» — это путь.

person Yitz    schedule 05.12.2010

Я думаю, что самая большая проблема с первым способом:

Если авторы mtl (или любой другой используемой вами библиотеки преобразователей) решат прекратить экспорт конструктора данных для ReaderT, он перестанет работать. Это случилось с монадой State в версии бамп с мтл 1 на мтл 2 и это довольно раздражает. Принимая во внимание, что ask является частью официального API Reader, и вы должны планировать, что он останется.

С другой стороны, я бы не считал первый способ неправильным.

person Jason Dagit    schedule 30.11.2010
comment
Монада State по-прежнему доступна с конструктором. Я думаю, вы имеете в виду ST-монаду. - person fuz; 04.12.2010
comment
Если вы посмотрите здесь: hackage.haskell.org/packages/archive/mtl/2.0.1.0/doc/html/src/ вы увидите, что конструктор данных для State больше не экспортируется. Это правда, что вы можете сделать много вещей, чтобы обойти это. - person Jason Dagit; 06.12.2010

По крайней мере разница в скорости есть.

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

random16  = State $ randomR (1,6) -- Using the internal representation
random16' = do
            s <- get
            (r,s') <- randomR (1,6) s
            put s'
            return r

С первым программа выполняется примерно за 6 секунд, а со вторым намного медленнее, занимая около 8 секунд. Я могу представить, что он похож для читателя, поэтому, возможно, используйте его вместо более четкого, когда важно время выполнения. Я использовал строгую версию для этого.

person fuz    schedule 01.12.2010