Haskell 中对不可变数据的可变引用

Mutable references to immutable data in Haskell

我想跟踪一系列不可变值中的一个 "current" 值。 在不为每个新值引入新引用的情况下,在 Haskell 中执行此操作的最佳方法是什么? 这是一个例子:

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

我不确定问题的真正含义。发布的例子 可以重写为使用 StateT Person IO monad,如下所示。

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
-}

如果需要真正的引用类型,可以使用 IORef Person,或者如果在 ST monad 中则可以使用 STRef。但在这种情况下,你必须在一些允许这些引用类型的 monad 中工作。相比之下,StateT Person m 适用于任何 monad m.

只是总结并向像我这样的其他 Haskell 新手提供提示 - 这是我最终确定的解决方案。这不是伪代码 :-) 而是 Frege(JVM 的 Haskell)有一些细微的符号差异。

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
-}

谢谢大家