Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making Read-Only functions for a State in Haskell

Tags:

haskell

monads

I often end up in a situation where it's very convenient to be using the State monad, due to having a lot of related functions that need to operate on the same piece of data in a semi-imperative way.

Some of the functions need to read the data in the State monad, but will never need to change it. Using the State monad as usual in these functions works just fine, but I can't help but feel that I've given up Haskell's inherent safety and replicated a language where any function can mutate anything.

Is there some type-level thing that I can do to ensure that these functions can only read from the State, and never write to it?

Current situation:

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

Ideally iReadData would probably have the type Reader MyState Int, but then it doesn't play nicely with the State. Having iReadData be a regular function seems to be the best bet, but then I have to go through the gymnastics of explicitly extracting and passing it the state every time it's used. What are my options?

like image 943
Squid_Tamer Avatar asked Feb 18 '15 15:02

Squid_Tamer


1 Answers

It's not hard to inject the Reader monad into State:

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

then you could say

iReadData :: Reader MyState Int
iReadData = do
    state <- ask
    return (getPieceOf state)

and call it as

x <- read $ iReadData

this would allow you to build up Readers into larger read-only sub-programs and inject them into State only where you need to combine them with mutators.

It's not hard to extend this to a ReaderT and StateT at the top of your monad transformer stack (in fact, the definition above works exactly for this case, just change the type). Extending it to a ReaderT and StateT in the middle of the stack is harder. You basically need a function

lift1 :: (forall a. m0 a -> m1 a) -> t m0 a -> t m1 a

for every monad transformer t in the stack above the ReaderT/StateT, which isn't part of the standard library.

like image 160
Jonathan Cast Avatar answered Oct 04 '22 22:10

Jonathan Cast