Pure File Reading (was: Dealing with configuration data)

oleg@pobox.com oleg@pobox.com
Thu, 26 Sep 2002 16:28:03 -0700 (PDT)


There is another solution to the problem of configurational
parameters. The main part of the solution is portable, does not depend
on any pragmas, does not use unsafe operations, does not use implicit
parameters, and does not require any modifications to the user code. I
must warn that it is also potentially vomit-inducing.

It seems that the problem at hand naturally splits into two phases:
building the configuration environment, and executing some code in
that environment. The phases are executed sequentially. The facts
suggest the use of a SupedMonad. SuperMonad is very well known and
often used, even by people who never heard of simpler monads.

The following code is an illustration. Suppose file '/tmp/a.hs'
contains the following user code, which is to run within the
configuration environment provided by the module Config. For
simplicity, our configuration is made of one Int datum, 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
  
We specifically illustrate the reading of the config item several
times.

The following code runs the first phase: reads the configuration,
build the SuperMonad and runs the SuperMonad.

> import System (system, ExitCode(ExitSuccess))
>
> myconfig_file = "/tmp/config"
>
> phaseII_var = "/tmp/Config.hs"
> phaseII_const = "/tmp/a.hs"
>
> 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 ("echo main | hugs " ++ phaseII_const) 
>                 >>= \ExitSuccess -> print "Phase II done"
>      
> main = readFile myconfig_file >>= writeConfig . read >>= runSuperIO

I did warn you, didn't I?

I have a hunch this solution will work with GHC even better than it
works with Hugs. Perhaps we can even play with some dynamic linking
tricks (like shared object initializers, etc). BTW, the solution above
is similar in spirit to the following trick in C++:
	Config config;
	int main() { /* pure functional C++ code here -- yes, it exists*/}
the constructor for 'config' is guaranteed to run before main().

Perhaps someone will implement Staged Haskell one day?