I have done some research on stackoverflow to find viable solution to the common problem of maintaining different states of global variable.
I found this elaborate question that addresses similar concern. It raises important issue of godlike global variable and that's an antipattern in Haskell. I perfectly understand that my situation is similar and I am trying to introduce this antipattern, but I don't really like the answer. It seems that Netwire
is an overkill for my task at hand, it could be done in much more simple and elegant way.
And I also found this one, but both question and answers address more general concerns and approaches, while I have concrete problem and, hopefully, concrete solution. What I also want (and could not find in previous questions) is to make a qualitative step in understanding of maintaining variable states through the simple example.
In the code below I am trying to update state of the godlike variable from two different places executing :load
and :new
commands, but, obviously, it doesn't work.
My question is how to modify following code in order to accommodate possibility of changing global variable value in a functional way? Should I throw away all the code because it represents imperative-style approach and replace it totally new parseInput
that follows rules of functional world? Should I replace global variable with something else? I assume I could use IORef
somehow, it seems appropriate. Or ST Monad
as this question/answer recommends.
What would be the easiest and straightforward step to address this problem without an overkill? I understand that I might need to better grasp the notion of Monads (State Monad in particular) and I am ready to learn how they could help with addressing this particular problem. But the articles I have read so far (this and this), didn't help much. I assume that State Monad is not really appropriate because my example does not have return value, only updated state. If I am wrong, could you please explain how and what missing links would help me to understand states in Haskell better?
{-# LANGUAGE QuasiQuotes #-}
import Text.Regex.PCRE
import System.Console.Haskeline
import TH (litFile)
import System.FilePath
import System.IO
import Control.Monad
import Control.Monad.IO.Class
import Data.List
mydata :: [Int]
mydata = [0]
saveDataToFile :: [Int] -> IO ()
saveDataToFile mydata = withFile "data.txt" WriteMode $ \h -> System.IO.hPutStr h (unwords $ map show mydata)
loadDataFromFile :: [Int]
loadDataFromFile = map read . words $ [litFile|data.txt|]
help :: InputT IO ()
help = liftIO $ mapM_ putStrLn
[ ""
, ":help - this help"
, ":q - quit"
, ":commands - list available commands"
, ""
]
commands :: InputT IO ()
commands = liftIO $ mapM_ putStrLn
[ ""
, ":show - display data"
, ":save - save results to file"
, ":load - loads data from file"
, ":new - generate new element "
, ""
]
parseInput :: String -> InputT IO ()
parseInput inp
| inp =~ "^\\:q" = return ()
| inp =~ "^\\:he" = help >> mainLoop
| inp =~ "^\\:commands" = commands >> mainLoop
| inp =~ "^\\:show" = do
liftIO $ putStrLn $ unwords $ map show mydata
mainLoop
| inp =~ "^\\:save" = do
liftIO $ saveDataToFile mydata
mainLoop
| inp =~ "^\\:load" = do
let mydata = loadDataFromFile -- <-- should update mydata
mainLoop
| inp =~ "^\\:new" = do
let mydata = mydata ++ [last mydata + 1] -- <-- should update mydata
mainLoop
| inp =~ ":" = do
outputStrLn $ "\nNo command \"" ++ inp ++ "\"\n"
mainLoop
| otherwise = handleInput inp
handleInput :: String -> InputT IO ()
handleInput inp = mainLoop
mainLoop :: InputT IO ()
mainLoop = do
inp <- getInputLine "% "
maybe (return ()) (parseInput) inp
greet :: IO ()
greet = mapM_ putStrLn
[ ""
, " MyProgram"
, "=============================="
, "For help type \":help\""
, ""
]
main :: IO ()
main = do
greet
runInputT defaultSettings (mainLoop)
PS. I use Template Haskell definitions (TH module) from this answer.