-- InternalComponent.hs
data ComponentState = ComponentState ...
instance Default ComponentState where ...
componentFunction :: (MonadState InternalComponentState m) => a -> m a
-- Program.hs
data ProgramState = forall cs. ProgramState {
componentState :: cs,
...
}
newtype MyMonad a = MyMonad { runMyMonad :: StateT ProgramState IO a }
myFunction a = do
s <- get
let cs = componentState s
let (r, cs') = runState (componentFunction a) cs
put $ s { componentState = cs' }
return r
我想要的是能够使用componentFunction
内的MyMonad
(在myFunction
中,如示例中所示),而不是对实际的类型感兴趣组件需要的状态。将组件状态保持在我自己的状态之内并不是一个要求,但就我在Haskell中使用状态的能力而言,那就是这样。
这实际上可以看作是另一种编程语言中有状态接口的实现的等价物:具有某种实现的接口的实例化提供了默认状态值,并且通过该接口调用的每个函数都可以修改该状态。在任何时候,用户都不会看到实施细节。
如果不清楚,则上述示例失败,因为myFunction
的实现无法证明记录选择器提供了合适的类型(因为它是存在的) ;至少我是如何理解的。
答案 0 :(得分:1)
您可以通过组件状态的类型对ProgramState
进行参数化,例如:有
data ProgramState cs = ProgramState { componentState :: cs }
这意味着您还必须从ComponentState
公开InternalComponent.hs
类型,但不是构造函数。这样您就可以为类型检查器提供一些内容,但不要向InternalComponent
的用户公开任何内部。
答案 1 :(得分:1)
首先,我建议您阅读Combining multiple states in StateT,了解其他可用选项。
由于在嵌套状态的情况下我们需要更新更复杂对象中的值,使用lens可以使生活更轻松(另请参阅this tutorial)。类型Lens' s a
的值知道如何在a
内达到s
类型的特定值以及如何修改它(即创建类型为s
的新值是相同的,除了修改后的a
)。然后我们可以定义辅助函数
runInside :: (MonadState s m) => Lens' s a -> State a r -> m r
runInside lens s = lens %%= (runState s)
鉴于a
上的镜头和有状态计算,我们可以将这样的计算提升到由s
参数化的有状态计算。该库允许我们使用Template Haskell生成镜头,例如:
{-# LANGUAGE RankNTypes, TemplateHaskell #-}
import Control.Lens.TH
data ProgramState cs = ProgramState { _componentState :: cs }
$(makeLenses ''ProgramState)
将生成componentState :: Lens' ProgramState cs
(实际上生成的函数将更加通用)。将它们组合在一起我们得到了
runInside componentState :: MonadState (ProgramState a) m => State a r -> m r
使用Typeable
我们可以更进一步,创建一个地图,自动创建或保持所要求的任何类型的状态。我一般不推荐这种方法,因为它可以避免Haskell的强类型系统检查,但在某些情况下可能会有用。
{-# LANGUAGE ExistentialQuantification, ScopedTypeVariables, RankNTypes #-}
import Control.Lens
import Control.Lens.TH
import Control.Monad.State
import Data.Map (Map)
import qualified Data.Map as Map
import Data.Typeable
data Something = forall a . Typeable a => Something a
type TypeMap = Map TypeRep Something
我们定义了一个通用的无类型容器,它可以包含任何Typeable
和一个将类型表示映射到它们的值的映射。
我们需要一些类来提供默认/起始值:
class Default a where
getDefault :: a
-- just an example
instance Default Int where
getDefault = 0
最后,我们可以创建一个给定任意Typeable
类型的镜头,通过查找其类型表示来关注它在地图中的值:
typeLens :: forall t . (Typeable t, Default t) => Lens' TypeMap t
typeLens = lens get set
where
set map v = Map.insert (typeOf v) (Something v) map
get map = case Map.lookup (typeRep (Proxy :: Proxy t)) map of
Just (Something v) | Just r <- cast v -> r
_ -> getDefault
所以你可以在你所在州的某个地方拥有TypeMap
,让所有有状态计算都使用它,无论他们需要什么状态。
然而,有一个大警告:如果两个不相关的计算碰巧使用相同类型的状态,他们会分享这个值很可能是灾难性的结果!因此,对计算的不同部分的状态使用显式记录会更加安全。