5

Background

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.

Problem

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

Syntax-highlighted version: ioref.hs

Thanks for your help!

Robin Green
  • 32,079
  • 16
  • 104
  • 187
Alex Vong
  • 483
  • 4
  • 15
  • 3
    `global` create new `SCM` with new `IORef`. You should reorganize your loop, where `global` will be performed once. – freestyle Aug 21 '17 at 11:29

1 Answers1

7

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
Zeta
  • 103,620
  • 13
  • 194
  • 236
  • Thank you, so my misconception was to think of it as a global. But in fact, it behaves more like a constructor, right? – Alex Vong Aug 21 '17 at 11:53
  • @AlexVong right. You can think of any `IO a` as an _action_ that will yield an `a` if executed. For example, `readLine :: IO String` will give you a `String` when you execute it, yet you don't expect `readLine` to return the same string for every call (it depends on the user's input). – Zeta Aug 21 '17 at 13:43