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

apfelmus apfelmus at quantentunnel.de
Sat Aug 11 10:22:00 EDT 2007


Brian Hulley schrieb:
> 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.
> 
> 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.

Indeed, Data.ByteString makes heavy use of unsafePerformIO and
inlinePerformIO. This is safe since it's just used for efficient memory
access and (de-)allocation, the ByteStrings themselves are immutable.

> If you don't use (unsafePerformIO), then the slightest need for mutable 
> data structures pollutes the entire interface.

Well, any code that wants to mutate or read this data structure has to
announce so in the type signature. However, it's debatable whether
certain forms of "mutation" count as pollution. In fact, the simplest
"mutation" is just a function  s -> s  . Haskell is throughly "polluted"
by such "mutations":

   (3+)         :: Int -> Int
   ([1,2]++)    :: [Int] -> [Int]
   insert "x" 3 :: Map String Int -> Map String Int

Of course, from the purely functional point of view, this is hardly
perceived as mutation since the original value is not changed at all and
still available. In other words, the need to "change" a value doesn't
imply the need to discard (and thus mutate) the old one.

Mutable data structures in the sense of ephemeral (= not persistent = 
update in-place) data structure indeed do introduce the need to work in 
ST since the old version is - by definition - not available anymore. 
This may be the right thing to do when the data structure is inherently 
used in a single-threaded fashion. However, most used-to-be ephemeral 
data structures have very good persistent counterparts in Haskell. In 
the end, the type just reflects the inherent difficulty of reasoning 
about ephemeral data structures. And that's what the quoted paper 
illustrates: persistent data structures are much easier to deal with.

> 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.

I don't know enough ML or have the background to judge whether this  ref 
  is really necessary, but I doubt that it can't be designed away.

> Haskell is designed so that any attempt at abstracting mutable
 > local state will infect the entire program

Depends on "local". In general, I think is a good thing. The type 
reflects how difficult your program really is, nothing more, nothing 
less. That's how it is: persistent data and prue functions are sooo much 
easier to reason about. Implicit side effects just sweep the difficulty 
under the carpet. (I imagine a tool that makes implicit side effects 
explicitly visible in the types of say C or ML programs. I guess that 
people would scream whole nights when seeing the output of this tool on 
their programs and thus discovering how complicated the code really is 
... Well, maybe not since they're used to it during debugging anyway.)

But if the state is really local, no infection of the entire program 
takes place! The best example is probably indeed the Haskell Graphics 
library. The are pure functions for constructing graphics

   over    :: Graphic -> Graphic -> Graphic
   polygon :: [Point] -> Graphic

and some IO-infected functions to draw those onto the screen

   drawInWindow :: Window -> Graphic -> IO ()

Now,  Graphic  may be implemented as an abstract data type and 
drawInWindow  does the workload of interpreting it. Or, and that's how 
HGL currently implementes it, it can be an IO-action that encodes how to 
draw it

   type Graphics = Draw ()
                ~= (Brush,Font,Pen) -> IO ()

That is, every graphic is "infested" with IO but that doesn't spread to 
the API. (It does a bit with  selectBrush  but that can be corrected.)

 > (modulo use of a highly dangerous function whose
> semantics is entirely unclear, depending on the vagaries of evaluation 
> strategy of the particular compiler)

(yes, unsafePerformIO clearly isn't for ephemeral data structures.)

> For example consider a simple GUI

Ah, the dreaded functional GUI problem. Yes, I agree that a good purely 
functional way of declaring a GUI has not been discovered yet, the 
signals and streams somehow miss something important.

 > 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.

It indeed seems that the "mathematics" behind GUIs are inherently 
difficult and the easiest framework to declare GUIs _for now_ is an 
imperative one. That doesn't mean that a simpler one doesn't exist. Note 
that word _declare_: you don't want to mutate state a priori, you want 
to say what is displayed when and somehow describe the data 
dependencies. Once a domain specific language to declare GUIs is found, 
I'm sure that it can naturally be embedded in Haskell.

> 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.

I'm not sure whether mutable state is the real goodie here. I think it's 
the ability to indpendently access parts of a compound state. In other 
words, the IORef created by  buffer  is a part of the total program 
state but you can access it independently. There is a functional idiom 
for that, see also

   Sander Evers, Peter Achten, and Jan Kuper. "A Functional Programming
   Technique for Forms in Graphical User Interfaces".
   http://www.st.cs.ru.nl/papers/2005/eves2005-FFormsIFL04.pdf


 > apfelmus wrote:
 >> However, most "genuinely imperative" things are often just a building
 >> block for a higher level functional model.

Thanks to your response, I think I can better articulate what I mean: 
with "purely functional", I mean "declarative", i.e. the ability to 
write down equations of how different things interact with each other 
and thus to abstract away their implementation. For example,

- For Graphics, I want to build a graphic from smaller ones and then 
draw it. I don't want to know how drawing is implemented and what 
mutable state might be involved.
- For a GUI, I want to write down the data dependencies and a library 
converts this to a mesh of mutable state.

That's what I mean with "higher level functional model". Syntactic sugar 
for applying monadic actions doesn't help with that. In fact, it intends 
to make it easier to write examples and miss the pattern/model behind. 
Likewise, allowing impure functions -> doesn't help with formulating or 
finding a model at all. Rather, it makes describing the model more 
error-prone.

Of course, I want to implement the imperative machinery too. But most 
often, deriving it from the underlying model is straightforward.

Regards,
apfelmus



More information about the Haskell-Cafe mailing list