[Haskell-cafe] Language support for imperative code. Was: Re: monad subexpressions

Brian Hulley brianh at metamilk.com
Wed Aug 8 11:00:55 EDT 2007


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.


More information about the Haskell-Cafe mailing list