Haskell: Monad transformers and global state

I am trying to learn Haskell. I'm trying to write a program that contains the "global state": Vars

. I want to change the state component (for example var1

) every time I call the function. The change can be a simple function on components (like +4). Also, it prints the changed component. Here is what I have done so far (but I am stuck). Edit: After running the code, I want to see the latest 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)

      

+3


source to share


1 answer


Try to solve your problem step by step, starting with simple and small details. This is an important skill in programming and FP teaches you this skill in a good way. Also, working with a monad State

and especially multiple effects in monad transformers helps you reason about effects and understand things better.

  • You want to update var1

    inside your immutable data type. This can only be done by creating a new object. So write a function like this:

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

    There are ways to write this function in Haskell that are much shorter, albeit less clear, but we don't care about those things right now.

  • Now you want to use this function inside State

    monad to update immutable state and by simulating mutability. What can you say about this function just by looking at its signature like change :: StateT Vars IO a

    :? We can say that this function has several effects: it has access to state Vars

    and can perform arbitrary actions IO

    . Also this function returns a value of type a

    . Hmm, this last one is weird. What is it a

    ? Which function should you return? In imperative programming, this function will be of type void

    or Unit

    . It just does something, it doesn't return everything. Only updates the context. Therefore, the type of the result must be ()

    . It might be different. For example, we can return a new oneVars

    after the change. But this is generally a bad programming approach. This makes this function more complex.

  • Once we understand what a type function should have (try to always start with defining types), we can implement it. We want to change our state. There are functions that operate on the partial state of our context. Basically, you are interested in this:

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

    modify

    The function takes a function that updates the state. After running this function, you can observe that the state changes according to the passed function. Now change

    it can be written as follows:

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

    You can implement modify

    (and therefore change

    using only functions put

    and get

    , which is a good exercise for beginners).

  • Now let's call functions change

    from some other function. What does the challenge mean in this case? This means that you are performing a monadic action change

    . This action changes your context, you don't care about it because it is ()

    . But if you run the get

    function (which binds the integer state to the variable) after change

    , you can watch the new change. If you only want to print the changed component, for example var1

    , you can use the function gets

    . And again, what type should it be sample

    ? What should he return? If on the caller side you are only interested in the 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 give you some idea of โ€‹โ€‹what's going on. Monad transformers take some time to get used to, although it's a powerful tool (not ideal, but extremely useful).

As a side note, I want to add that this function can be written much better using the common Haskell design patterns. But you don't need to take care of them right now, just try to understand what's going on here.

+2


source







All Articles