I am a Schemer starting to learn Haskell. I am trying to implement a Scheme interpreter in C, following chapter 4 of SICP. It turns out programming directly in C is too hard. So I decide to first prototype in Haskell. With the help of Write Yourself a Scheme in 48 Hours, I have implemented everything except variables, closures and environment.
The modification to IORef
doesn't persist between invocations of main
. I expect the program to print (False) (True) (True) (True)... but in fact it prints (False) (True) (False) (True) (False) (True)...
Strip-down version of the code:
import Data.IORef
data SCM = Environment (IORef Bool) SCM | Empty'Environment
global :: IO SCM
global = Environment <$> newIORef False <*> pure Empty'Environment
print'' :: SCM -> IO ()
print'' ls =
case ls of
Empty'Environment -> pure ()
Environment first rest -> readIORef first >>= putStr . show >> print'' rest
print' :: SCM -> IO ()
print' ls = putStr "(" *> print'' ls *> putStrLn ")"
main :: IO ()
main = global >>=
\ls -> case ls of
Empty'Environment -> pure ()
Environment first _ -> print' ls *>
modifyIORef first (const True) *>
print' ls *>
main
Thanks for your help!
We can cut your example down to main = (global >>= loop) >> main
. The problem is that global
is not a single global variable, instead its a IO SCM
, an action that will create the global value. Let us rename it:
createSCM :: IO SCM
createSCM = Environment <$> newIORef False <*> pure Empty'Environment
Now the name is closer to the truth. We create a new SCM
every time we call that function. So your program works like this:
main = (createSCM >>= loop) >> main
= (createSCM >>= loop) >> (createSCM >>= loop) >> main
= (createSCM >>= loop) >> (createSCM >>= loop) >> ...
As you can see we create new SCM
all the time. Therefore you don't get the expected behavior.
The solution is simple. Create your global
and loop
explicitly:
main = do
global <- createSCM
let loop = do
...
loop
loop