Haskell:在 FreeMonad 解释器中使用 MonadState 进行记忆
Haskell: Memoising using MonadState inside a FreeMonad Interpreter
鉴于我有以下 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,这样如果已经为给定的 Id
检索到 Thing
,那么就不会再调用 SomeApi
假设我已经知道如何使用 get
和 put
编写备忘版本,但我遇到的问题是 运行 MonadState
inside 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
returns (.. , MonadState ThingMap m) => m a
和 evalStateT
期望 StateT ThingMap m a
.
使用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
等效地,如果您首先使用 hoistFT lift
将 MyDsl m a
提高到 MyDsl (StateT Int m) a
,则可以使用 iterT
,如下所示:
runMyDsl :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslT m a -> m a
runMyDsl dsl = evalStateT (iterT run (hoistFT lift dsl)) Map.empty
这使得 dsl
变成了 MyDsl (StateT Int m) a
,它实际上不涉及任何状态更新,尽管 run
确实涉及状态转换。
鉴于我有以下 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,这样如果已经为给定的 Id
检索到 Thing
,那么就不会再调用 SomeApi
假设我已经知道如何使用 get
和 put
编写备忘版本,但我遇到的问题是 运行 MonadState
inside 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
returns (.. , MonadState ThingMap m) => m a
和 evalStateT
期望 StateT ThingMap m a
.
使用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
等效地,如果您首先使用 hoistFT lift
将 MyDsl m a
提高到 MyDsl (StateT Int m) a
,则可以使用 iterT
,如下所示:
runMyDsl :: (MonadLogger m, MonadIO m, MonadCatch m) => MyDslT m a -> m a
runMyDsl dsl = evalStateT (iterT run (hoistFT lift dsl)) Map.empty
这使得 dsl
变成了 MyDsl (StateT Int m) a
,它实际上不涉及任何状态更新,尽管 run
确实涉及状态转换。