Global variables?

oleg@pobox.com oleg@pobox.com
Mon, 3 Feb 2003 19:35:14 -0800 (PST)


Richard Uhtenwoldt wrote:
> (2) the global variable has a lexical scope that extends over
> the bulk of the program.

> It strikes me as a simple and obvious application of lexical
> scope, and I am surprised that it received no mention in the
> discussions on this list and in Hughes's paper.

Something like that has been mentioned before:
  http://www.haskell.org/pipermail/haskell-cafe/2002-September/003423.html

If the global variables are used to store parameters that are read
from configuration files at the beginning and are not changed since,
it's possible to make such "global variables" _ordinary_ top-level
variables scoped through the entire program. No unsafe operations, no
pragmas, no implicit variables, and no reliance on the IO monad for
variable access are needed.

The previous article said the approach was ugly. I'd like to take it
back. The approach relies on an ability to dynamically compile and
link in a program -- something that we all do and something that is
specifically emphasized in the GHCi documentation.


The following code is snipped from the previous article and adjusted
for GHC. The latter is used as a meta-evaluator/dynamic linker of
sorts.

Suppose file '/tmp/a.hs' contains the following user program. Suppose
the program needs a global, configurational variable named 
Config.config_item. 

>>> File "/tmp/a.hs"

> import Config (config_item)
>
> foo = "foo shows: " ++ (show config_item)
>
> bar = "bar shows: " ++ (show config_item)
>
> main = do
>   print foo
>   print bar
>   print foo

Realistically a user program would do something more with the global
variables than just showing them. We specifically illustrate multiple
access to the global variable, to indicate that such access poses no
trouble (in contrast to the unsafePerformIO approach). We also note
that user computations 'foo' and 'bar' do not have to be annotated
with the type and the name of the global variable -- in contrast to
the implicit variable approach. Furthermore, foo and bar are pure
functions rather than IO actions. 

The value of the global variable Config.config_item is computed by
reading it from the configuration file. The computation occurs before
running of the main function of the user program. The following code
accomplishes the trick

*>>> File "/tmp/b.lhs"
*>>> To run this code, do 
*>>>   ghci -package posix b.lhs
*>>> Be sure /tmp/config exists and contains an integer value

> import System (system, ExitCode(ExitSuccess))
> import Posix(executeFile)
>
> myconfig_file = "/tmp/config"
>
> phaseII_var = "/tmp/Config.hs"
> phaseII_const = "/tmp/a.hs"
> phaseII_eval = "ghc --make "
> phaseII_result = "/tmp/a.out"
>
> nl = "\n"
>
> writeConfig :: Int -> IO ()
> writeConfig num =
>   do
>    writeFile phaseII_var $
>         concat
>          ["module Config (config_item) where", nl,
>           "config_item =", show num, nl]
>  
>
> runSuperIO () = system (phaseII_eval ++ 
>                         phaseII_const ++ " -o " ++ phaseII_result)
>                 >>= \ExitSuccess -> 
>                          executeFile phaseII_result False [] Nothing
>
> main = readFile myconfig_file >>= writeConfig . read >>= runSuperIO

We can indeed be sure that the configurational variable is set before
the first attempt to access it.