我经常遇到这样的情况,使用 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 转换器堆栈顶部的
ReaderT
和 StateT
并不难(实际上,上面的定义完全适用于这种情况,只需更改类型即可)。将其扩展到堆栈中间的 ReaderT
和 StateT
更难。你基本上需要一个功能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/