2016-04-13 5 views
2

У меня есть несколько действий монады State. Некоторые из действий принимают решения, основанные на текущем состоянии и других входах, которые могут генерировать результат. Два типа действий вызывают друг друга.Составляющие действия трансформатора состояния и состояния

Я смоделировал эти два типа действия с помощью State и StateT Maybe. Следующий (надуманный) пример показывает мой текущий подход.

{-# LANGUAGE MultiWayIf #-} 

import Control.Monad (guard) 
import Control.Monad.Identity (runIdentity) 
import Control.Monad.Trans.State 

type Producer  = Int -> State [Int] Int 
type MaybeProducer = Int -> StateT [Int] Maybe Int 

produce :: Producer 
produce n 
    | n <= 0 = return 0 

    | otherwise = do accum <- get 
        let mRes = runStateT (maybeProduce n) accum 

        if | Just res <- mRes -> StateT $ const (return res) 
         | otherwise  -> do res <- produce (n - 1) 
               return $ res + n 

maybeProduce :: MaybeProducer 
maybeProduce n = do guard $ odd n 
        modify (n:) 

        mapStateT (return . runIdentity) $ 
         do res <- produce (n - 1) 
          return $ res + n 

Я факторизовал отделение проверки от действий (таким образом, превращая их в простые действия государства), потому что сама проверка очень запутанная (80% работ) и обеспечиваю необходимые привязки в действии. Я не хочу рекламировать действия State до StateT Maybe, потому что он представляет собой неточную модель.

Есть ли лучший или более простой способ, который мне не хватает? В частности, мне не нравится дуэт mapStateT/runStateT, но это кажется необходимым.

PS: Я знаю пример, на самом деле является Writer, но я использовал State, чтобы лучше отражать реальный случай

+2

'mapStateT' отлично, ИМО. Я просто использую 'Just. runIdentity' в качестве аргумента. – arrowd

+0

Примерно такой же вопрос (хотя и более теоретический): http://stackoverflow.com/questions/4138671/combining-statet-and-state-monads – gcnew

ответ

1

Я не хочу, чтобы продвигать State действий на StateT Maybe либо, потому что она представляет неточную модель.

Что вы подразумеваете под "promotion"? Я не могу сказать, какой из них вы имеете в виду:

  1. Перепишите определения State действий так, что их типа теперь StateT Maybe, несмотря на то, что они не полагаются на Maybe вообще;
  2. Используйте функцию адаптера, которая преобразует State s a в StateT s Maybe a.

Я согласен с отвергая (1), но для меня это означает либо:

  • Перейти к (2). Одним из полезных инструментов для этого является использование mmorph library (blog entry).
  • Перепишите все действия с State s a на использование Monad m => StateT s m a.

Во втором случае типа совместимого с любой Монадой m, но не позволяет коду принять любые конкретные базовые монады, так что вы получите ту же чистоту, как State s a.

Я бы дал mmorph выстрел. Отметьте, что:

  • State s a = StateT s Identity a;
  • hoist generalize :: (MFunctor t, Monad m) => t Identity a -> t m a;
  • И что специализируется на hoist generalize :: State s a -> StateT s Maybe a.

EDIT: Это ничего не стоит, что существует изоморфизм между типами State s a и forall m. StateT s m a, учитывая эти обратные функции:

{-# LANGUAGE RankNTypes #-} 

import Control.Monad.Morph 
import Control.Monad.Trans 
import Control.Monad.Trans.State 
import Control.Monad.Identity 

fwd :: (MFunctor t, Monad m) => t Identity a -> t m a 
fwd = hoist generalize 

-- The `forall` in the signature forbids callers from demanding any 
-- specific choice of type for `m`, which allows *us* to choose 
-- `Identity` for `m` here. 
bck :: MFunctor t => (forall m. t m a) -> t Identity a 
bck = hoist generalize 

Так Monad m => StateT s m a и mmorph решения, эффективно , тоже самое. Я предпочитаю использовать здесь mmorph.

+0

Да, «продвигать» я имел в виду (1). «Monad m => StateT s m a» - это аккуратный трюк, хотя и не явный, и я немного заблуждаюсь от точки зрения API. Конечно, это совершенно разумно: «Я не зависим от типа внутренней монады». Спасибо, что mmorph выглядит как правильный инструмент для работы, спасибо. – gcnew

+0

«Monad m => StateT s m a' - это аккуратный трюк, хотя и не явный, и я немного заблуждаюсь от точки зрения API». Я согласен, что это менее понятно, но я бы сказал, что это, строго говоря, * не * вводит в заблуждение, потому что тип будет ** изоморфным ** для 'State s a'. См. Мое редактирование ответа. –