我经常遇到这样的情况,使用 State monad 非常方便,因为有很多相关的函数需要以半命令的方式对同一块数据进行操作。

一些函数需要读取 State monad 中的数据,但永远不需要更改它。在这些函数中像往常一样使用 State monad 工作得很好,但我不禁觉得我已经放弃了 Haskell 的固有安全性并复制了一种任何函数都可以改变任何东西的语言。

我可以做一些类型级别的事情来确保这些函数只能从 State 读取,而从不写入吗?

现在的情况:

iWriteData :: Int -> State MyState ()
iWriteData n = do
    state <- get
    put (doSomething n state)

-- Ideally this type would show that the state can't change.
iReadData :: State MyState Int
iReadData = do
    state <- get
    return (getPieceOf state)

bigFunction :: State MyState ()
bigFunction = do
    iWriteData 5
    iWriteData 10
    num <- iReadData  -- How do we know that the state wasn't modified?
    iWRiteData num

理想情况下, iReadData 的类型可能为 Reader MyState Int ,但它与 State 的匹配效果不佳。让 iReadData 成为一个常规函数似乎是最好的选择,但是我必须经历每次使用时明确提取并传递状态的体操。我有哪些选择?

最佳答案

Reader monad 注入(inject) State 并不难:

read :: Reader s a -> State s a
read a = gets (runReader a)

那么你可以说
iReadData :: Reader MyState Int
iReadData = do
    state <- ask
    return (getPieceOf state)

并将其称为
x <- read $ iReadData

这将允许您将 Reader s 构建为更大的只读子程序,并将它们注入(inject) State 仅在您需要将它们与增变器结合的地方。

将其扩展到 monad 转换器堆栈顶部的 ReaderTStateT 并不难(实际上,上面的定义完全适用于这种情况,只需更改类型即可)。将其扩展到堆栈中间的 ReaderTStateT 更难。你基本上需要一个功能
lift1 :: (forall a. m0 a -> m1 a) -> t m0 a -> t m1 a

对于 t/ReaderT 上方堆栈中的每个 monad 转换器 StateT ,它不是标准库的一部分。

关于haskell - 在 Haskell 中为状态创建只读函数,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/28587132/

10-14 04:19