Search code examples
haskellimmutabilityfrege

Mutable references to immutable data in Haskell


I'd like to keep track of a "current" value in a succession of immutable values. What is the best way to do that in Haskell without introducing a new reference for every new value? Here is an example:

data Person = Person {name, level, topic :: String }
    deriving(Show)

dierk :: Person
dierk = Person "Dierk" "confident" "Java"

works :: Person -> String
works person = name person ++ " is " ++ level person ++ " in " ++ topic person


main _ = do
    putStrLn $ works dierk
    -- do more with "current" topic
    putStrLn $ works dierk {level= "proficient", topic="Groovy"}
    -- do more with "current" topic
    putStrLn $ works dierk {level= "dabbling", topic="Haskell"}
    -- do more with "current" topic

Solution

  • I'm not sure about what the question really asks for. The posted example can be rewritten to use the StateT Person IO monad as follows.

    import Control.Monad.State
    
    data Person = Person {name, level, topic :: String }
       deriving Show
    
    dierk :: Person
    dierk = Person "Dierk" "confident" "Java"
    
    works :: Person -> String
    works person = name person ++ " is " ++ level person ++ " in " ++ topic person
    
    main :: IO ()
    main = flip evalStateT dierk $ do
       -- use the current topic
       lift . putStrLn . works =<< get
       -- change the current topic
       modify (\t -> t{level= "proficient", topic="Groovy"})
       lift . putStrLn . works =<< get
       -- change the current topic
       modify (\t -> t{level= "dabbling", topic="Haskell"})
       lift . putStrLn . works =<< get
    
    {- Output:
    Dierk is confident in Java
    Dierk is proficient in Groovy
    Dierk is dabbling in Haskell
    -}
    

    If instead a real reference type is wanted, one could use IORef Person, or STRef if in the ST monad. But in such case, you must be working inside some monad allowing these reference types. By comparison, StateT Person m works in any monad m.