Global variables?
Richard Uhtenwoldt
ru@river.org
Sun, 02 Feb 2003 23:36:36 -0800
Andrew J Bromage writes:
>> John Hughes wrote a nice pearl on the subject, see
>>
>> http://www.math.chalmers.se/~rjmh/Globals.ps
>
>Nice!
I do not think it is nice: I do not like any of the
solutions Hughes considers in that paper because this
problem can be handled much more simply with lexical
scope and the IO monad.
Just to get our bearings, let us first consider the solution that
uses unsafePerformIO, which neither Hughes nor I prefer:
>globalVar :: IORef Int
>globalVar = unsafePerformIO $ newIORef 0
>foo = fff aaa bbb ccc
>bar = ggg xxx yyy zzz
>main = mmm >> nnn >> ooo
where the lines
>foo = fff aaa bbb ccc
>bar = ggg xxx yyy zzz
stand in for a typically much larger chunk of code --the bulk
of the program, let us call it.
The solution I prefer replaces that last with
>main=do
> globalVar<-newIORef 0
> let
> foo = fff aaa bbb ccc
> bar = ggg xxx yyy zzz
> mmm >> nnn >> ooo
In other words, to be painfully explicit, the solution I prefer
arranges the program so that
(1) the global variable is the result of an IO computation just
like every other IO computation; and
(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.
Of course, whenever you want to read or to write my global
variable, you must be in the IO monad, but Hughes's solution
requires being in a state monad. I do not consider the IO monad
any worse than any other state monad.
Am I the only one who prefers the above "lexical scope" solution
to all the solutions in Hughes's paper and given previously on
this list?
Indentation
One may raise the following objection to the "lexical scope"
solution: levels of indentation are a scarce resource and a
solution that consumes two of those levels before the program
proper even starts is wasteful.
I do not disagree with that objection; but I believe that the
best response to the objection is not to abandon the the
"lexical scope" solution but rather to adjust the syntactical
definition of Haskell and of the do notation. E.g., we can
eliminate one level of indentation by doing what GHCi does and
eliminate the requirement for the "main=do" in programs.
(This has the added benefit of shortening the Haskell version of
"hello, world" to putStrLn "hello, world."
Do not underestimate the attractiveness of a language with a
short "hello, world" to the more practically-inclined
programmers of the world.)
>Why isn't RefMonad in hslibs?
>It makes perfect sense for there to be more than one kind of "ref"
>for a given monad. Indeed, sometimes it's important. Quite often, I
>use a custom ref built on top of IORef which supports Ord, as this is
>needed for hash consing.
I think it is cleaner and simpler to ask the compiler
maintainers to add an IORef instance to Ord. They know
better how to implement it so it is fast. And it is
the solution that puts the least cognitive demmand on
readers of your program. (It's the easiest to learn,
I mean.)
I am not unalterably opposed to class RefMonad or to implicit
variables. If someone makes a convincing argument that they are
the best solution to an issue or problem, I will use them.
But Hughes's paper does not convince me.
Note that the global-variable solution I prefer will be more
familiar to non-Haskell programmers. (To appreciate it, they
have to "grok" monads, which can be a high hurdle, but they have
to "grok" monads to appreciate Hughes's solution, too.)
I wonder if Hughes's solution was motivated by a desire to avoid
having to stay in the IO monad during all access to global
variables.
Several on this list have stated or implied that the IO monad is
something to be resisted when possible. I do not agree with
that position. But a general defense of the IO monad will take
much more time than I have today.