所以我正在开发一个可扩展的应用程序框架,该框架的一个关键部分是能够在许多不同的状态类型上运行状态monad;我已经设置好了,可以运行嵌套状态monad;但是我需要的一个关键特性是嵌套状态的monad能够在全局状态下运行动作;在之前的项目中,我设法使用一些复杂的Free Monads来解决这个问题,但现在我正在使用mtl而且我有点卡住了。
以下是一些背景信息:
newtype App a = App
{ runApp :: StateT AppState IO a
} deriving (Functor, Applicative, Monad, MonadState AppState, MonadIO)
data AppState = AppState
{ _stateA :: A
, _stateB :: B
}
makeLenses ''AppState
我正在尝试定义类似的内容:
liftApp :: MonadTrans m => App a -> m App a
liftApp = lift
当然,由于MonadTrans的属性,这很好用,但现在的诀窍是当我有这样的动作时:
appAction :: App ()
appAction = ...
type ActionA a = StateT A App a
doStuffA :: ActionA ()
doStuffA = do
thing1
thing2
liftApp appAction
...
这在我的应用程序中编译;但是当在App本身中运行时会出现这个诀窍:
myApp :: App ()
myApp = do
...
zoomer stateA doStuffA
我在撰写zoomer
时遇到了麻烦;这是一次尝试:
zoomer :: Lens' AppState s -> StateT s App r -> App r
zoomer lns act = do
s <- get
(r, nextState) <- runStateT (zoom lns act) s
put nextState
return r
问题是runStateT (zoom lns act) s
本身就是一个App,但它也会产生一个AppState,然后我需要put
来获取更改。这意味着<- runStateT
的Monadic部分导致的任何更改都会被put nextState
覆盖。
我很确定我不应该像这样嵌套两组MonadState AppState,但我不确定如何使它工作,因为mtl不允许我嵌套多个MonadState由于功能依赖
我也开始尝试反转它并让App成为外部变压器:
newtype App m a = App
{ runApp :: StateT AppState m a
} deriving (Functor, Applicative, Monad, MonadState AppState, MonadIO, MonadTrans)
希望使用MonadTrans
允许:
liftApp = lift
但GHC不允许这样做:
• Expected kind ‘* -> *’, but ‘m’ has kind ‘*’
• In the second argument of ‘StateT’, namely ‘m’
In the type ‘StateT AppState m a’
In the definition of data constructor ‘App’
我不确定无论如何都会有用......
这就是问题所在,我希望能够将App
monad嵌套在StateT
的任意级别中以某种方式在App
内运行。
有什么想法吗?谢谢你的时间!!
答案 0 :(得分:1)
沿着user2407038's comment,这种类型......
type ActionA a = StateT A App a
......对我来说有点奇怪。如果您要使用zoom stateA
,则其他状态是AppState
的一部分。假设您可以修改A
子状态而不触及AppState
的其余部分(否则您首先不想要zoom
),您应该能够简单地定义,例如......
doStuffA :: StateT A IO ()
...然后将其带到App
:
zoomer :: Lens' AppState s -> StateT s IO r -> App r
zoomer l a = App (zoom l a)
GHCi> :t zoomer stateA doStuffA
zoomer stateA doStuffA :: App ()
如果您更喜欢纯doStuffA
...
pureDoStuffA :: State A ()
...你只需要在适当的地方return
进入IO
......
GHCi> :t zoomer stateA (StateT $ return . runState pureDoStuffA)
zoomer stateA (StateT $ return . runState pureDoStuffA) :: App ()
...或者,使用mmorph进行计算拼写:
GHCi> :t zoomer stateA (hoist generalize pureDoStuffA)
zoomer stateA (hoist generalize pureDoStuffA) :: App ()
答案 1 :(得分:0)
初步说明:我采取了非常不同寻常的步骤,发布了第二个答案,因为我觉得我的前一个答案很好,而这个问题从一个完全不同的角度来看这个问题。
鉴于you have found a solution already,这个答案现在有点没有实际意义,但无论如何我觉得我应该在这里扩展我的最新评论。在其中,我曾问过:
你说你希望“嵌套状态的monad能够在全局状态下运行”。但是,一旦你有了它,我们真的可以将嵌套monad视为状态monads 的特定子状态吗?
我会说我们做不到。使用您建议实现的功能,也许您会为每个子状态正式拥有某种不同的StateT
层;但是,如果您可以在这些图层中运行全局状态操作,那么它们与整体状态之间的界限就会模糊。就隔离而言,您可以使用单片AppState
。
data AppState = AppState
{ _stateA :: A
, _stateB :: B
}
makeLenses ''AppState
data Shared = Shared -- etc.
一种简单的布线方式是使用两个StateT
层:
newtype App a = App
{ runApp :: StateT AppState (StateT Shared IO) a
} deriving (Functor, Applicative, Monad, MonadState AppState, MonadIO)
现在,您可以对共享状态执行lift
次操作,例如StateT A (StateT Shared IO)
,然后使用App
将其转到App . zoom stateA
。但是,使用嵌套的StateT
图层可能有点尴尬。另一种方法是将Shared
纳入AppState
...
data AppState = AppState
{ _stateA :: A
, _stateB :: B
, _shared :: Shared
}
makeLenses ''AppState
newtype App a = App
{ runApp :: StateT AppState IO a
} deriving (Functor, Applicative, Monad, MonadState AppState, MonadIO)
...然后编写可以访问子状态和共享状态的镜头:
data Substate a = Substate
{ _getSubstate :: a
, _sharedInSub :: Shared
}
makeLenses ''Substate
-- There are, of course, lots of other ways of spelling these definitions.
subA :: Lens' AppState (Substate A)
subA = lens
(Substate <$> _stateA <*> _shared)
(\app (Substate a s) -> app { _stateA = a, _shared = s })
subB :: Lens' AppState (Substate B)
subB = lens
(Substate <$> _stateB <*> _shared)
(\app (Substate b s) -> app { _stateB = b, _shared = s })
现在你需要zoom
,例如subA
代替stateA
。还有一些额外的样板需要定义这些镜头,但如果需要可以减轻。
顺便说一下, lens 中没有组合器可以捕获这种模式 - 例如,类型为Lens s t a b -> Lens s t c d -> Lens s t (a, c) (b, d)
的东西 - 因为通常它不会生成合法的镜头 - {正如我们所做的那样,{3}}才能正常运作。也就是说,通过稍微改组,我们可以用the lenses have to be disjoint来表达我们正在做的事情,尽管我不确定这是否有利于此:
data ComponentState = ComponentState
{ _stateA :: A
, _stateB :: B
}
makeLenses ''AppState
newtype App a = App
{ runApp :: StateT (ComponentState, Shared) IO a
} deriving (Functor, Applicative, Monad, MonadState AppState, MonadIO)
subA :: Lens' (ComponentState, Shared) (A, Shared)
subA = alongside stateA simple
subB :: Lens' (ComponentState, Shared) (B, Shared)
subB = alongside stateB simple
(如果你不喜欢使用裸对,你可以定义AppState
和Substate a
同构,并使用相应的Iso
将它们提交给{{1 }。)