在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 = ...