在Haskell中为状态生成只读函数
我经常会遇到这样的情况:使用在Haskell中为状态生成只读函数,haskell,monads,Haskell,Monads,我经常会遇到这样的情况:使用状态monad非常方便,因为有许多相关函数需要以半强制方式对同一数据段进行操作 有些函数需要读取状态monad中的数据,但永远不需要更改它。在这些函数中像往常一样使用Statemonad工作得很好,但我不禁觉得我已经放弃了Haskell的固有安全性,复制了一种任何函数都可以变异的语言 我是否可以做一些类型级别的事情来确保这些函数只能从状态读取,而不能写入 当前情况: iWriteData :: Int -> State MyState () iWriteData
状态
monad非常方便,因为有许多相关函数需要以半强制方式对同一数据段进行操作
有些函数需要读取状态monad中的数据,但永远不需要更改它。在这些函数中像往常一样使用State
monad工作得很好,但我不禁觉得我已经放弃了Haskell的固有安全性,复制了一种任何函数都可以变异的语言
我是否可以做一些类型级别的事情来确保这些函数只能从状态
读取,而不能写入
当前情况:
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
iWriteData::Int->State MyState()
iWriteData n=do
state将读取器
monad注入状态
,并不难:
read :: Reader s a -> State s a
read a = gets (runReader a)
那么你可以说
iReadData :: Reader MyState Int
iReadData = do
state <- ask
return (getPieceOf state)
对于ReaderT
/StateT
上方堆栈中的每个monad转换器t
,它不是标准库的一部分。我建议将状态
monad包装在newtype
中,并为其定义一个monadrader
实例:
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleContexts #-}
import Control.Applicative
import Control.Monad.State
import Control.Monad.Reader
data MyState = MyState Int deriving Show
newtype App a = App
{ runApp' :: State MyState a
} deriving
( Functor
, Applicative
, Monad
, MonadState MyState
)
runApp :: App a -> MyState -> (a, MyState)
runApp app = runState $ runApp' app
instance MonadReader MyState App where
ask = get
local f m = App $ fmap (fst . runApp m . f) $ get
iWriteData :: MonadState MyState m => Int -> m ()
iWriteData n = do
MyState s <- get
put $ MyState $ s + n
iReadData :: MonadReader MyState m => m Int
iReadData = do
MyState s <- ask
return $ s * 2
bigFunction :: App ()
bigFunction = do
iWriteData 5
iWriteData 10
num <- iReadData
iWriteData num
{-#语言泛化newtypederiving}
{-#语言MultiParamTypeClasses}
{-#语言灵活语境#-}
导入控制
进口控制单体状态
导入控制.Monad.Reader
数据MyState=MyState Int派生显示
newtype App a=App
{runApp'::状态MyState a
}衍生
(函子)
,实用的
,单子
,MonadState MyState
)
runApp::App a->MyState->(a,MyState)
runApp app=runState$runApp'应用程序
实例Monawarder MyState应用程序,其中
问=得到
本地fm=App$fmap(fst.runApp m.f)$get
iWriteData::MonadState MyState m=>Int->m()
iWriteData n=do
MyState s m Int
iReadData=do
MyState sjcast和bhelkir给出了非常好的答案,这正是我第一个想到将Reader
嵌入State
的想法
我认为有必要讨论一下你问题的这个半侧面:
在这些函数中像往常一样使用State
monad工作得很好,但我不禁觉得我已经放弃了Haskell的固有安全性,复制了一种任何函数都可以变异的语言
这确实是一个潜在的危险信号。我总是发现State
最适用于具有“小”状态的代码,这些状态可以包含在runState
的单个简短应用程序的生命周期内。我的示例是对可遍历的数据结构的元素进行编号:
import Control.Monad.State
import Data.Traversable (Traversable, traverse)
tag :: (Traversable t, Enum s) => s -> t a -> t (s, a)
tag i ta = evalState (traverse step ta) init
where step a = do s <- postIncrement
return (s, a)
postIncrement :: Enum s => State s s
postIncrement = do result <- get
put (succ result)
return result
示意图示例
example :: State (A, B) Whatever
example = do foo <- substate fst (,b) action1
bar <- substate snd (a,) action2
return $ makeWhatever foo bar
-- Can only touch the `A` component of the state
action1 :: State A Foo
action1 = ...
-- Can only touch the `B` component of the state
action2 :: State B Bar
action2 = ...
example::State(A,B)Whatever
example=do foo为什么不将当前状态传递给需要它的函数呢?最好将read
定义为read x=get(runReader x)
。然后它实际上适用于Reader
和State
的最新定义,即ReaderT
和StateT
。我接受了这个答案,因为它最直接地回答了这个问题,而且可能是很多人都在寻找的答案。我真的很喜欢其他答案,@bheklillr的答案当然给了我很多思考的食物。因此我们得到了mtl
与transformers
的哲学争论?如果你只为StateT写一个例子,你可以让这个想法变得更简单,这就是新类型的状态:实例Monad m=>monadranderr(StateT r m),其中ask=get;本地=带状态
。这给了您相同的属性,我们可以将iReadData的类型签名“降级”为monawarder MyState m=>m Int
@ChrisDrost,这难道不会与monawarder r m=>monawarder r(StateT s m)
的现有实例重叠吗?此外,孤儿实例是不受欢迎的。@bheklillr这是个好问题;我不知道Monawarder已经有StateT的例子了。是的,在GHCi中涂鸦会发现它会大声抱怨重叠实例,即使底层monad不符合其中一个实例的约束。
import Control.Monad.State
import Data.Traversable (Traversable, traverse)
tag :: (Traversable t, Enum s) => s -> t a -> t (s, a)
tag i ta = evalState (traverse step ta) init
where step a = do s <- postIncrement
return (s, a)
postIncrement :: Enum s => State s s
postIncrement = do result <- get
put (succ result)
return result
-- | Extract a piece of the current state and run an action that reads
-- and modifies only that piece.
substate :: (s -> s') -> (s' -> s -> s) -> State s' a -> State s a
substate extract replace action =
do s <- get
let (s', a) = runState action (extract s)
put (replace s' s)
return a
example :: State (A, B) Whatever
example = do foo <- substate fst (,b) action1
bar <- substate snd (a,) action2
return $ makeWhatever foo bar
-- Can only touch the `A` component of the state
action1 :: State A Foo
action1 = ...
-- Can only touch the `B` component of the state
action2 :: State B Bar
action2 = ...