
Jon Cast writes
I, personally, haven't written a program whose bulk will fit in a single file in several years, and I doubt I ever will again. So, support for separate compilation is a necessity. How do you intend to handle this?
Hmm; good point. I see I have been guilty of a careless, uncharitable reading of Hughes's paper! the first three pages of Hughes's paper refer to "module" and the equivalent word "package", but I skipped over those references on first reading. Moreover, when at the top of page 4 Hughes writes, "but such a variable is bound by a \-expression, and not at top level," I missed the relevance of the fact that only top-level binding are eligible for export from a module. So, my previous post was unfair to Hughes because Hughes's four solutions to the "global-mutables" problem seem to be able to span module boundaries, whereas my solution does not. Specifically, my "lexical-scope" solution allocates a global variable via the code fragment
newIORef 0 >>= \globalVariable-> lala
but there is no way in Haskell to export a lambda-bound variable, which is what globalVariable is. There's more bad news for my solution. It is a very common pattern in the imperative world for a module to contain a piece of "hidden" (not exported) state. In fact, this is one of the defining characteristics of the OOP paradigm. And the addG, removeG, frontG, isEmptyG that figure so large in Hughes's paper constitute and interface to a piece of hidden state, so it has module-like characteristics, even though Hughes does not identify it as a module. If we try to use my lexical-scope solution to implement this very common pattern, as follows, we run into trouble if we try to put it in its own module because the variables addG, removeG, frontG and isEmptyG are not bound at the top level and thus not eligible for export.
main=do ioref<-newIORef ... let addG = ... removeG = ... frontG = ... isEmptyG = ... in --code that refers to addG, removeG, etc, goes here.
In summary, my lexical-scope solution works only when the solution does not need to cross module boundaries. We could change Haskell's module system so that it allows the export of lambda-bound and let-bound variables. Or we could use the implicit-variables solution reccommended in Hughes's paper. I'm not going to try to make a decisive argument for either one, but in the rest of this post I want to put in a few good words for the alternative of changing Haskell's module system. What incited me to write the rest of this post is not module systems per se. Rather, I wish to use modules systems and Hughes's global-variables paper as examples with which to express my scepticism or concern over the design decisions behind extensions to Haskell whose purpose is to support imperative programming. The rest of this post (61 lines long) is not terribly important or penetrating, so stop reading now if you value your time! Maybe I should have just deleted it rather than posting. Haskell's module system was probably not designed with imperative programming in mind. In other words, it was designed before we had ambitions for Haskell to be an imperative programing language as well as a declarative programming language. (Of course, not everyone in the Haskell community has such ambitions.) maybe it is time to re-examining the design decisions behind the module system. The imperative world has 27 years of experience with module systems, starting with Modula in 1975. It is likely that some of the design decisions that have stood the test of time in the imperative world can be applied directly to Haskell. We probably do not want slavishly to copy into Haskell a module system from the imperative world because no imperative language has Haskell's type classes, nor AFAIK its algebraic datatypes and pattern matching. Both of these features probably overlap in functionality with module systems popular in the imperative world. A good language designer will want to understand the areas of overlap before designing a new module system for Haskell. There is an analog in the imperative world to Haskell's notion of a variable that receives the "result" of a monadic computation: it is the assignment statement. If the module systems that have withstood the test of time in the imperative world can export entities that have been give a value by an assignment statement, I am perfectly happy for Haskell's module system to change so that it can do the same thing. I see nothing wrong with looking toward the imperative world for ways of implementing intrinsically imperative things (like global mutables). And if the result of importing (pun not intended) from the imperative all have types in the IO monad, that's okay with me: I see nothing toxic or evil about the IO monad. One might raise the objection that if Haskell simply copies the design decisions of the imperative world, then there is no reason for Haskell to exist: we might just as well use an imperative language instead. Two responses to that: (1) for the programmer to be able to switch gracefully between imperative and declarative programming is not an ability offered by imperative languages. (2) even if one abandons declarative programming altogether, the idea of doing imperative programming in a referentially-transparent way, with every side-effect wrapped in a monad, is by itself exciting to me. Finally, as an appendix, I collect previous threads on this subject and the related subject of reading a configuration file or getting the commandline arguments at the start of one's program. Hughes's paper http://www.math.chalmers.se/~rjmh/Globals.ps readFileOnce::String->String http://haskell.org/pipermail/haskell/2002-September/010514.html http://haskell.org/pipermail/haskell/2002-September/010515.html http://haskell.org/pipermail/haskell/2002-October/010551.html Oleg's solution http://haskell.org/pipermail/haskell/2002-September/010519.html http://www.haskell.org/pipermail/haskell-cafe/2002-January/002589.html