在Haskell中为状态创建只读函数

Squ*_*mer 7 monads haskell

我经常最终处于使用Statemonad 非常方便的情况,因为有很多相关的函数需要以半命令的方式对同一块数据进行操作.

有些函数需要读取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
Run Code Online (Sandbox Code Playgroud)

理想情况下iReadData可能会有类型Reader MyState Int,但它不能很好地与State.有iReadData是一个普通的功能似乎是最好的选择,但我必须要经过的明确提取和每一个它的使用时间的推移它的状态的体操.我有什么选择?

Jon*_*ast 7

注入Readermonad 并不难State:

read :: Reader s a -> State s a
read a = gets (runReader a)
Run Code Online (Sandbox Code Playgroud)

然后你可以说

iReadData :: Reader MyState Int
iReadData = do
    state <- ask
    return (getPieceOf state)
Run Code Online (Sandbox Code Playgroud)

并称之为

x <- read $ iReadData
Run Code Online (Sandbox Code Playgroud)

这将允许您将Readers 构建为更大的只读子程序,并将它们注入到State需要将它们与mutator组合的位置.

不难将其扩展到ReaderTStateT你的单子转换堆栈的顶部(其实,上面的定义的工作原理完全针对这种情况,只是改变类型).它延伸到ReaderTStateT在堆栈的中间更难.你基本上需要一个功能

lift1 :: (forall a. m0 a -> m1 a) -> t m0 a -> t m1 a
Run Code Online (Sandbox Code Playgroud)

对于/ t上面的堆栈中的每个monad变换器,它不是标准库的一部分.ReaderTStateT

  • 最好将`read`定义为`read x = gets(runReader x)`.然后它实际上与`ReaderT`和`StateT`一起使用了最近对`Reader`和`State`的定义. (3认同)

bhe*_*ilr 6

我建议State在a中包装monad newtypeMonadReader为它定义一个实例:

{-# 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
Run Code Online (Sandbox Code Playgroud)

这肯定是@ jcast解决方案的更多代码,但它遵循将变换器堆栈实现为新类型包装器的传统,并且通过坚持使用约束而不是固化类型,您可以对代码的使用提供强有力的保证,同时提供最大的灵活性重复使用.任何使用您的代码将能够延长你的App同时仍然使用自己的变压器iReadData,并iWriteData如预期.您也不必Reader使用read函数将每个调用包装到monad中,这些MonadReader MyState函数与Appmonad中的函数无缝集成.