Dierk
Dierk

Reputation: 1308

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

Upvotes: 1

Views: 291

Answers (2)

Dierk
Dierk

Reputation: 1308

Just to wrap up and give hints to other Haskell newbies like me - here is the solution that I finally settled with. This is no pseudo-code :-) but Frege (a Haskell for the JVM) that has some minor notational differences.

module Person where

import frege.control.monad.State

data Person = Person {name, level, topic :: String }

derive Show Person

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

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

printCurrentPerson :: StateT Person IO ()
printCurrentPerson = do
    person <- StateT.get            -- independent of any particular person reference
    StateT.lift $ println $ works person

updateCurrentPerson :: Monad m => String -> String -> StateT Person m ()
updateCurrentPerson level topic = do
    StateT.modify (\person -> Person.{level= level, topic=topic} person)

usingMutableRefsToImmutableState :: Person -> IO ((),Person)
usingMutableRefsToImmutableState start =
    flip StateT.run start $ do
        printCurrentPerson
        updateCurrentPerson "proficient" "Groovy"
        printCurrentPerson
        StateT.lift $ println "-- user input could influence which selection is 'current' "
        updateCurrentPerson "dabbling" "Haskell"
        printCurrentPerson

main = do -- using the StateT transformer to work in combination with any monad (here: IO)
    (_, lastPerson) <- usingMutableRefsToImmutableState dierk
    println "-- a second round with relaying the last person"
    _ <- usingMutableRefsToImmutableState lastPerson
    return ()

{-  output
    Dierk is confident in Java
    Dierk is proficient in Groovy
    -- user input could influence which selection is 'current' 
    Dierk is dabbling in Haskell
    -- a second round with relaying the last person
    Dierk is dabbling in Haskell
    Dierk is proficient in Groovy
    -- user input could influence which selection is 'current' 
    Dierk is dabbling in Haskell
-}

Thank you all.

Upvotes: 0

chi
chi

Reputation: 116139

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.

Upvotes: 3

Related Questions