world as value

Martin Wierich martinw@cs.kun.nl
Thu, 17 Jun 1999 18:23:22 +0200


Hi everybody,

Obviously my issue about the world as value paradigm raised interest, thank's
for that. I am surprised, that some poeple conclude, i/o in functional languages
doesn't work soundly.

What did I do ?

I constructed a concrete set of values and considered them to constitute the
type "World". Using these values I defined some functions whose semantics was
implicit, because they were pure (see my preceeding message). I have shown, that
the semantic of these functions does not match the semantic of their non pure
counterparts from the StdFile module, concluding that the model is to weak to
express the semantics of the StdFile functions. So I asked for proposals how to
enrich this simple World model, and got some answers. I also encouraged the
readers to think about the question how the implementation of the StdFile's
function could be changed so that their semantic fits into my simple model, but
no answer came. (Don't get me wrong: I'm not going to propose, that we _should_
change this implementation, I merely made a thought experiment.)

To make things clear: For me a model of the *World type is a set of values,
which are mathematical objects (e.g. a set, relation, graph, number). Assigning
semantics to a function that operates on the *World means constructing a
function in the usual mathematical manner.

Fergus Henderson first made an outline for a world model.

> The solution to this problem is to say that the semantics of each
> individual I/O operation provided by the standard library is to
> (1) delay some unspecified amount of time (thus creating a new world,
> reflecting the side effects of any other programs running
> concurrently, and the changes in the state of clocks and other
> time-dependent hardware in the world) and then (2) do the
> operation.

This is approximately identical to what I have in mind. I love to be concrete:

    World = R x WorldStates

Read: The World is the cartesian product of the set of real numbers and the set
of WorldStates. The interpretation for the real number is the current _time_ in
seconds relatively to a chosen origin. To give every i/o operation a semantic we
need to choose a certain set WorldStates and a function delay:

   delay :: World -> World

According to Fergus Henderson every i/o performing function would be like e.g.:

fopen :: String Int *World_ -> (Bool, *File_, *World_)
fopen filename mode world
    # (time_after_fopen, diskContents) = delay world
      (success, file, diskContents) = fopen_atomic filename mode diskContents
    = (success, file, (time_after_fopen, diskContents))

We see, that fopen can change the whole world and not only the Files
subenvironment. The fopen_atomic function delivers the result of opening a file
without any other sideeffects. The delay function itself should in my opinion be
seen as:

delay world
 # (time_interval, world) = interval world
 = simulate time_interval world

The interval function calculates how much time the has been elapsed since the
last i/o operation of the program that is under consideration. The simulate
function calculates all the sideeffects from other programs that changed the
world since then by _simulating_ these programs.

I want to show an implication of the upper model. Consider the following
function:

     Start world = timeout 300 heavy_comutation world
       where
         timeout :: Int Int !*World -> (Maybe Int, !*World)
         timeout time_in_secs computation world
           = "when <computation> can be reduced to an Int within <time_in_secs>
              seconds, then Just this Int otherwise Nothing"
         heavy_computation :: Int -> Int
         heavy_computation
           = "a time consuming computation"

If heavy_computation reduces to 42, is it correct, when the upper program
returns Nothing while the following program returns (Just 42) ?

      Start world = timeout 300 42 world

Think about it. But take into account, that the world parameter could contain
the program _itself_, so that it knows, whether there was a "42" or
"heavy_computation" written down in the source code.

Let's suppose one world model for two different programs. If these two programs
terminate at the same time, then they will deliver the same result: the world!
They do this because they simulate each other. This is the way to give these
programs a sound functional semantic.

I think there are no simpler models, if we want to take time into account. I
think there are simpler models, if we don't take time into account. The simple
model from my last message could be preserved, if closing of a file would happen
only (from the operating systems view) when the program _terminates_. No other
programs could interfere then and introduce _time_ dependence. The price for the
simple model is a less powerful interface to the (file) world. This issue is
connected to my question:

>    What semantics do we _want_ to assign to the *World ?

The most important thing for me is: We API designers for i/o functions have it
in our hand. We should be careful. It's very easy to implement a function that
returns the current operating system time. It can be done in ten minutes. But
the consequences for the semantic are immense. On the other hand, if we try to
offer only functions, that lead to an easy semantic, then people might complain,
they are not powerful enough. What a tradeoff.

Comments are welcome.

greetings
  Martin Wierich