
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