(This was originally discussed on the Yahoo pragprog list; I think it's important enough to present it on LtU as well.) In our experience, true state (destructively assignable entities) is essential for reasons of program modularity (which for this discussion I take as meaning: the ability to change one part of a program in a significant way without changing the rest of the program). Threaded state, e.g., monads as used by Haskell or DCGs as used by Prolog, cannot substitute for it. Here is a simple example to show the difference between true state and threaded state. Consider a system consisting of modules A, B, C, D, and E, all written in a purely functional way. Module A calls B, which calls C, which calls D, which calls E. Now let's say I want to instrument E, to count how many times it is performing some operation, and I want to observe this from A. If I want to program this in a purely functional way (e.g., in the pure subset of Haskell), then I am obliged to change the interfaces of all the modules A, B, C, D, and E (*). If I have true state, then I can get by with changes in just A and E. In other words, instead of changing everything on the path, I can get by with changing things on the ends. This is a major difference that has all kinds of software engineering ramifications. Here's how it works. (See CTM for more information: section 4.7.2 in the June 5 draft or section 4.8.2 in a later draft explains the idea with a similar example.) The new module E has two interfaces, IE1 and IE2. IE1 is the original interface (purely declarative) and is used as before. IE2 is a new interface with two operations: InitCounter which initializes the internal E counter to zero and GetCounter which returns the value of the internal E counter. (The E counter is of course incremented when we call IE1.) Then all we have to do is pass an IE2 reference to module A, which can then do what it likes with the counter. Modules B, C, and D see nothing at all. You can only do this if you have true state. Here is one way of seeing why. True state lets information pass "underground" between two interfaces, i.e., the information passes without any apparent connection between them. This is because the connection is the shared state, which is shared by the two interfaces yet hidden from the outside. The shared state is a kind of covert information channel: it lets a module pass information to other modules (or to itself in the future) without anybody else seeing it. There is a final point regarding static typing. The usual static type systems, such as Haskell's, do not support this technique. The question is, how can type systems be extended to support this technique? I am not an expert on type systems, but I leave this question open for you. One hint may be given by the preceeding paragraph: maybe the covert information channel should be typed. ------------------------- (*) You may counter that using monads can solve this problem in Haskell. I.e., just write all your modules in a monadic way from the start. Then there are three possibilities: Either you do it explicitly, in which case your whole program becomes more complicated, even those parts that have nothing to do with the instrumentation. For example, what happens if there are also modules A', B', C', and D', where A' calls B', etc., and D' calls E? They all have to carry around the instrumentation counter.

Or you decide to hide the monadic operations from the programmer and let the compiler add them. In which case you have just implemented a stateful language on top of Haskell.

Or you can use unsafePerformIO. In which case you are using true state. And I have not yet mentioned the real issue, which is declarative concurrency. Two concurrent activities execute independently. If you impose a state threading using monads, then they are no longer independent because you have to impose an order. This may seem bearable in a sequential system (such as the pure subset of Haskell) but it is simply not possible in a concurrent system (such as the declarative concurrent subset of Oz, which is pure and yet concurrent).

Posted to functional by Peter Van Roy on 10/22/03; 3:04:50 AM



