假设我必须实现一个功能
f :: Foo -> ReaderT Bar IO Baz
我必须传递给消费者(即我将调用c f
),其中//Foo
被强加给函数的消费者,并且消费者将使用不同的s 重复调用该函数。Bar
Baz
Foo
我是否有机会在连续调用之间保留一些本地f
状态?
在某种程度上,我已经通过IORef
更改签名来实现这一点f
f :: IORef S -> Foo -> ReaderT Client IO a
并将其部分应用到我预先初始化的某种状态:
s <- initState :: IO (IORef S)
c $ f s
这样,完全应用的可以通过一些pdate 函数f
改变monads
中的 tate :IO
atomicModifyIORef'
u
f s x = do
liftIO $ atomicModifyIORef' s u
-- ...
然而,上面的解决方案对我来说很自然,因为s
tate 确实是一个全局可变状态,它不仅可以通过调用来修改f
,还可以通过与 并发运行的程序的其他部分来修改f
。
但现在我需要f
拥有一些不能被其他任何人修改的私有状态。这让我想到了StateT
变压器,但我真的不知道我可以在这种情况下应用它。
手头的实际用例是我想要notify
在实现通知服务器的上下文中使用有状态函数。请参阅此处的实现示例。在这种情况下,Foo -> ReaderT Bar IO Baz
实际上是MethodCall -> ReaderT Client IO Reply
( ReaderT Client IO a
is DBusR a
)。
正如您所看到的,无论我对 做什么notify
,我最终都必须将一个MethodCall -> DBusR Reply
函数传递给export
,client
并且该函数将被多次调用,并且预计每次都会返回,所以在我看来,保持状态的唯一方法是它的闭包,即我必须notify
在 之前再提供一个参数MethodCall
,并将其部分应用于初始状态,正如我上面所解释的。MethodCall
每次传递a 时改变状态的唯一方法是让第一个附加参数成为真正可变的状态,例如IORef
我上面提到的。
是这个吗?
想象一下 的签名
f
是:并且您以某种方式“使用
StateT
”f
通过一系列调用来将本地状态线程化f
。这意味着两次连续调用f
具有相同Foo
参数的结果读取器使用相同的Bar
参数运行:可能会导致两种不同的
baz1
结果baz2
,具体取决于当地状态的变化,对吗?换句话说,这将违反引用透明度。因此,您可以保持状态的唯一
f :: Foo -> ReaderT Bar IO Baz
原因是因为您有可用的基础 monadIO
,因此您提出的任何解决方案都必须使用IO
效果来维持状态。StateT
然而,它很容易在 的定义中使用f
,同时实际上将状态维护为IORef
的私有状态f
。如果您使用以下方式编写核心f
逻辑StateT
:你可以建立一种
f
工厂:在IO中,你可以这样写:
请注意,由于 是
sref
在 中创建的f_factory
,因此它包含在生成的闭包中f_with_state
,但在其他地方不可用。并且,在 的核心定义中f
,您可以只使用get
、put
、modify
等,而不必担心实际f_with_state
函数正在恢复和保存私有sref
IORef 中的状态。