apfelmus wrote:
However, most "genuinely imperative" things are often just a building
block for a higher level functional model. The ByteString library is a
good example: the interface is purely functional, the internals are
explicit memory control. It's a bad idea to let the internal memory
control leak out and pollute an otherwise purely functional program with
IO-types.
Hi Apfelmus,

This is a really interesting discussion that touches on issues I'm currently working with (I'm designing a strict version of Haskell to explore various ideas about modules, namespace management, and how to get really efficient machine code but without losing the convenience of accurate garbage collection etc, but I'm having difficulties deciding between the monadic path and the "impure" path), so I've forked this new thread.

Regarding the quote above, if the API must hide explicit memory control from the user the only way I can see of doing this would be to use (unsafePerformIO), which really is unsafe since Haskell relies on the fact that mutable operations can't escape from the IO monad in order to get away with not having to impose a value restriction as in ML.

If you don't use (unsafePerformIO), then the slightest need for mutable data structures pollutes the entire interface. For example in the excellent paper you quoted

 N. Ramsey and J. Dias.
 An Applicative Control-Flow Graph Based on Huet's Zipper
 http://www.eecs.harvard.edu/~nr/pubs/zipcfg-abstract.html 
<http://www.eecs.harvard.edu/%7Enr/pubs/zipcfg-abstract.html>


the authors are pleased to have found an "Applicative" solution, and indeed their solution has many useful and instructive aspects. However on page 111, hidden away in the definition of their API function to create a label, is a call to (ref 0) !!!! ;-) The equivalent implementation in Haskell would completely destroy all hope of using this in a pure context and force all use of the API into the IO monad.

Haskell and ML seem to stand at opposite poles. Haskell is designed so that any attempt at abstracting mutable local state will infect the entire program (modulo use of a highly dangerous function whose semantics is entirely unclear, depending on the vagaries of evaluation strategy of the particular compiler) whereas people have been struggling in the ML community for at least 15 years to try and find a way to hide the fact that mutable storage is being used (cf attempts to find a proper solution to the unsoundness introduced by polymorphism and ref without having to use imperative/weak type variables etc).

Meanwhile, C++, C#, and Java programmers get a type system (thinking only about static methods using generics/templates) that seems to me no less powerful than that of the prenex polymorphism of ML, yet without any of the unsoundness problems, and therefore without the need of a value restriction (afaiu this is because their template/generic definitions stand in for an infinite family of monomorphic bindings instead of ML which tries unsuccessfully to make one piece of memory represent each element of an infinite family of values simultaneously).

Not only this, but there seems to me to be many problems for which it is natural to think in terms of objects with identity and mutable state. I can readily discard the concepts of deep inheritance hierarchies and open recursion but this still leaves the problem of identity and localised mutability.

For example consider a simple GUI with 2 edit widgets, a splitter, and a single buffer that contains the text being edited. The idea is that you should be able to use either edit widget to interact with the same buffer eg to edit at different locations in the buffer. A simple imperative solution might be something like:

   main = do
           buffer <- createBuffer
           edit1 <- createEdit buffer
           edit2 <- createEdit buffer
           splitter <- createSplitter (wrapWidget edit1) (wrapWidget edit2)
           runMessageLoopWith splitter

Here it's really clear what's happening, and different objects in the program correspond exactly to how I think about what I'm trying to do ie I want to create individual objects with identity and then plug them together. I don't want to have to bother about passing state around, or having to define a new data type every time I want a different configuration of widgets. Thus the ability to abstract mutable state gives to my mind by far the best solution.

In contrast, all the pure functional GUIs that I've seen are just wrappers around someone else's imperative code, and moreover, they exchange the simplicity of the object oriented imperative API for a veritable mindstorm of unbelievably heavy, clunky, slow, inefficient, inextensible, hard to understand encodings that seem to me to have the effect of turning a high level language into some kind of circuit board (I'm thinking of arrows etc).

Indeed everyone seems to be using either WxWidgets or Gtk2Hs which kind of proves my point that in this domain at least imperative solutions are generally simpler than functional ones...

Also, many "genuinely concurrent" things just aren't. An example are
UNIX pipes like say

  cat Main.hs | grep "Warm, fuzzy thing"

The OS creates a processes for "cat" and "grep" running concurrently and
"cat" passes a stream of characters to "grep". By blocking on the reader
and the write side, "grep" reads what "cat" writes in real-time. Well,
but that's just good old lazy evaluation!
True, but the above program is just a trivial transformation from input to output ie just using the computer as a calculator. For interactive programs you need to be able to implement a different kind of laziness, because the challenge is not just how to compute some output from some input, but how to maintain a mapping between input and output that respects some invariant in the presence of dynamic deltas to the input as the user enters information into the program, ensuring that the amount of computation done between each time the display is rendered is proportional to the delta rather than the entire input.

So in summary for me the good things about Haskell are nothing to do with functional purity or laziness but are instead to do with the fact that it's basically the only statically typed modern language (apart from OCaml, MLton, and MLKit) that has a free compiler to unburdened native code (apart from the LGPL gnuMP hard wired into the runtime in ghc which is one reason I'm writing my own compiler so I can actually put my own programs on the web to sell...) and accurate garbage collection (otherwise I'd be happy to use C++). (The great type system, good syntax, well designed foreign function interface, ability to control use of APIs with great precision by using phantom type permissions in the monad(s), and of course millions of interesting research papers and discussions on this list are an extra bonus.)

Summary of summary: Haskell is a good language for imperative programming, and the pure functional subset has failed to yield a practical GUI even after more than a decade of research. I've wasted at least a whole year of sleepless nights trying to work out how to represent an efficient GUI without using mutable data, and the feeling that there should be a pure solution made me abandon a perfectly workable imperative GUI that I started 18 months ago, that if I'd written it in any other language without this pure/impure conundrum, would have made me very happy with it.

Best regards, Brian.
_______________________________________________
Haskell-Cafe mailing list
Haskell-Cafe@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Reply via email to