Является ли / Оборачивание функций в преобразователь монад считается плохой практикой? - PullRequest
7 голосов
/ 30 ноября 2010

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

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

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

find a = ReaderT (lookup a)

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

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

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

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

Ответы [ 3 ]

9 голосов
/ 30 ноября 2010

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

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

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

3 голосов
/ 05 декабря 2010

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

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

Но пока ваша «первая возможность» - это путь.

3 голосов
/ 01 декабря 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 секунд. Я могу представить себе, что это похоже на читателя, поэтому, возможно, используйте этот вместо более ясного, когда важно время выполнения. Я использовал строгую версию для этого.

...