简体   繁体   中英

Haskell: Monad transformers and global state

I'm trying to learn Haskell. I'm trying to write a programm that contains a "global state": Vars . I want to change a component of the state (eg var1 ) each time I call a function. The change can be a simple function on the components (eg +4). Also, it prints out the component changed. Here is what I've done so far (but I'm stuck). Edit: after running the code I want to see the recent version of the global state.

import Control.Monad.State
import Control.Monad.IO.Class (liftIO)

data Vars = Vars {
 var1 :: Int,
 var2 :: Float
} deriving (Show)

sample :: StateT Vars IO a
sample = do 
        a <- change
        liftIO $ print a
        -- I want to call change again and apply more change to the state


change  :: StateT Vars IO a
change  = do
        dd <- get
         -- I don't know what to do next!

main = do 
  runStateT sample (Vars 20 3)
  evalStateT sample (Vars 20 3)

Let's try to solve your problem step-by-step starting from easier and small parts. It's important skill in programming and FP teaches you that skill in nice way. Also, working with State monad and especially with several effects in monad-transformers helps you to reason about effects and understand things better.

  1. You want to update var1 inside your immutable data type. This can be done only by creating new object. So let's write such function:

     plusFour :: Vars -> Vars plusFour (Vars v1 v2) = Vars (v1 + 4) v2 

    There exist ways in Haskell to write this function much shorter though less understandable, but we don't care about those things now.

  2. Now you want to use this function inside State monad to update immutable state and by this simulate mutability. What can be told about this function only by looking at its type signature: change :: StateT Vars IO a ? We can say that this function have several effects: it has access to Vars state and it can do arbitrary IO actions. Also this function returns value of type a . Hmm, this last one is strange. What is a ? What this function should return? In imperative programming this function will have type void or Unit . It just do things, it doesn't return everything. Only updates context. So it's result type should be () . It can be different. For example we might want to return new Vars after change. But this is generally bad approach in programming. It makes this function more complex.

  3. After we understood what type function should have (try to always start with defining types) we can implement it. We want to change our state. There're functions that operates with stateful parts of our context. Basically, you interested in this one:

    modify :: Monad m => (s -> s) -> StateT sm ()

    modify function takes function which updates state. After you run this function you can observe that state is modified according to passed function. Now change can be written like this:

     change :: StateT Vars IO () change = modify plusFour 

    You can implement modify (and thus change using only put and get functions which is nice exercise for beginner).

  4. Let's now call change function from some other function. What does calling mean in this case? It means that you execute monadic action change . This action changes your context, you don't care about it's result because it's () . But if you run get function (which binds whole state to variable) after change you can observe new change. If you want to print only changed component, like var1 you can use gets function. And, again, what type should sample have? What should it return? If on the caller side you're interested only in resulting state, then, again, it should be () like this:

     sample :: StateT Vars IO () sample = do change v1 <- gets var1 liftIO $ print v1 change v1' <- gets var1 liftIO $ print v1' -- this should be v1 + 4 

This should add you some understanding of what is happening. Monad transformers require some time to get used to them though it's a powerful tool (not perfect but extremely useful).

As as a side note I want to add that these function can be written much nicer using common Haskell design patterns. But you don't need to care about those right now, just try to understand what's going on here.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM