Dealing with configuration data

Hal Daume III hdaume@ISI.EDU
Wed, 25 Sep 2002 16:34:02 -0700 (PDT)

I don't mean to troll, but this isn't what I meant.  Suppose we have:

    data Configuration = ...  -- config data

    globalConfig :: IORef Configuration
    globalConfig = unsafePerformIO (newIORef undefined)

Now, we define an unsafe function to read the configuration:

    getConfig :: Configuration
    getConfig = unsafePerformIO $ readIORef globalConfig

Okay, this is "bad" but I claim it's okay, iff it is used as in:

    main = do configuration from calls to getConfig...
       writeIORef globalConfig configuration
       return ()

now, we have doStuff :: IO a.  doStuff is allowed (even in its pure
methods) to use getConfig.  I claim that this is safe.  I could be
wrong; this is only a hand-waiving argument.  Why?

The first reference in the program to globalConfig is through a
writeIORef.  This means that at this point globalConfig gets evaluated and
thus a ref is created.  Immediately we put a value in it.

Now, when doStuff runs, since it is an action run *after* the call to
writeIORef, provided that it doesn't also write to 'globalConfig' (which I
mentioned in my original message), any call to getConfig is deterministic.

I could be wrong...please correct me if I am.

Hal Daume III

 "Computer science is no more about computers    |
  than astronomy is about telescopes." -Dijkstra |

On Thu, 26 Sep 2002, Nick Name wrote:

> On Wed, 25 Sep 2002 16:06:29 -0700 (PDT)
> Hal Daume III <hdaume@ISI.EDU> wrote:
> > I don't feel bad about doing
> >  this because GHC does this itself for its own configuration :).
> I am going to show you that using unsafePerformIO where there really are
> side effects leads to unpredictable results, and is generally wrong in a
> lazy language. Don't hate me for this :)
> Consider this example (supposing that a Config is represented by an
> Int):
> storeConfig :: Int -> ()
> readConfig :: Int
> They both are obtained through the use of "unsafePerformIO".
> Now, say I got this code:
>  (storeConfig 0,storeConfig 1,readConfig,storeConfig 0,readConfig)
> What is this 5-uple supposed to evaluate to?
> First of all, this depends on order of evaluation. We can't say that all
> the elements of the tuple will be evaluated, so we can't tell if the
> fifth readConfig will evaluate to 0 or 1 (if the third storeConfig is
> never evaluated, readConfig will evaluate to 0, else to 1) This is one
> of the causes of the use of monads: ensuring correct order of
> evaluation.
> Second, suppose we were able to force order of evaluation (which
> shouldn't be allowed, in a lazy language). We still can't say what the
> last "readConfig" would evaluate to, since we don't know if the compiler
> is substituting equals for equals (I am expecting a lazy functional
> language to do this). 
> If the compiler does, the last readConfig is equal to the first (in
> fact, by the use of unsafePerformIO, you have told the compiler that
> both the functions storeConfig and readConfig are pure, which is not
> true) and will evaluate to 1, else it will evaluate to 0. And, besides,
> the compiler should also substitute the second "storeConfig 0" with the
> result of the first occurrence, so it would not evaluate the second
> "storeConfig" at all.
> This is another example of the need for monads: allowing program
> transformations, first of all substituting equals for equals.
> This is why (even if, by enough knoweledge of the implementation, we
> could), by only relying on the semantics of a lazy language, we can not
> have functions with side effects.
> If it wasn't so, they would not have invented monads, believe me.
> I apologize, as always, for my terrible english, and hope I have been
> clear.
> Vincenzo Ciancia
> _______________________________________________
> Haskell-Cafe mailing list