2

鉴于我有以下 DSL(使用 Free Monad)及其解释器:

data MyDslF next =
    GetThingById Int (Thing -> next)
  | Log Text next

type MyDslT = FT MyDslF

runMyDsl :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslT m a -> m a
runMyDsl = iterT run
  where
    run :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslF (m a) -> m a
    run (Log message continue)      = Logger.log message >> continue
    run (GetThingById id' continue) = SomeApi.getThingById id' >>= continue

我想在内部更改解释器以使用 MonadState ,以便如果 aThing已经为给定检索到Id,则没有第二次调用SomeApi

假设我已经知道如何使用getand编写记忆版本put,但我遇到的问题是在MonadState内部运行runMyDsl。我在想解决方案看起来类似于:

type ThingMap = Map Int Thing

runMyDsl :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslT m a -> m a
runMyDsl = flip evalStateT mempty . iterT run
  where
    run :: (MonadLogger m, MonadIO m, MonadCatch m, MonadState ThingMap m) => MyDslF (m a) -> m a
    run ..

但是类型不对齐,因为run返回(.. , MonadState ThingMap m) => m aevalStateT期望StateT ThingMap m a

4

1 回答 1

2

使用iterTM代替iterT

runMyDsl :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslT m a -> m a
runMyDsl dsl = evalStateT (iterTM run dsl) Map.empty
  where
  run (Log message continue)      = logger message >> continue
  run (GetThingById id' continue) = do
    m <- get 
    case Map.lookup id' m of
      Nothing -> do 
         thing <- getThingById id' 
         put (Map.insert id' thing m)
         continue thing
      Just thing -> continue thing

等效地,iterT如果您首先提高MyDsl m aMyDsl (StateT Int m) ausing ,则可以使用hoistFT lift,如下所示:

runMyDsl :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslT m a -> m a
runMyDsl dsl = evalStateT (iterT run (hoistFT lift dsl)) Map.empty

这使得dslMyDsl (StateT Int m) a实际上不涉及任何状态更新,但run确实涉及状态转换。

于 2016-07-15T11:31:44.353 回答