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