
George Russell
Your implementation is probably much simpler than mine because you don't implement withEmptyDict. I'm really quite keen about withEmptyDict, because one of the MAJOR conceptual problems I have with unsafePerformIO global variables is that you only get one universe, corresponding to the Haskell program.
I think global variables are a lot less evil if they behave as if they were dynamically scoped, like Lisp special variables. That is, there is a construct which gives the variable a new mutable binding visible in the given IO action. It's used more often than assignment. Assignment is still available though. In Common Lisp implementations these variables are not inherited by threads: each thread starts with toplevel bindings of dynamic variables. I think this is wrong and they should be inherited. In my language Kogut they are inherited. With threads it makes a difference that the variable gets a new binding, not just a new value. The old binding is still mutable by threads which have not shadowed it. When the scope of the new binding finishes, the value restored in this thread might be different than the value from the time the scope was entered, if other threads have changed it in the meantime. In Haskell it would be a new kind of reference, parallel to IORef and MVar. In principle dynamic variables need not to be defined at the toplevel. In Lisp they are effectively always toplevel variables (even if declared locally); in my language they can be created in arbitrary places, e.g. as fields of objects. But usually they are toplevel. It would be pointless to *not* have toplevel dynamic variables, because their purpose is to avoid manually threading them through all actions which need them. This is an alternative design to Haskell's implicit parameters. It's different in that it applies to the IO monad only (dynamic variables obviously can't be read from pure code) and that the fact that an action uses a particular variable is not reflected in its type. Their primary use is to provide a default setting used in deep places in a computation, with the assumption that usually a single setting applies to the whole computation started from a given place. Like the random number generator, the default output handle (not the *internals* of stdOut as a statefulobject, but binding the stdOut variable to different handles, not possible in Haskell), the current locale or individual settings implied by the locale (I don't know yet how "inheritable" settings should be designed, like the locale as a whole and its parts). -- __("< Marcin Kowalczyk \__/ qrczak@knm.org.pl ^^ http://qrnik.knm.org.pl/~qrczak/