Re: Applicability of FP unchanging things to real world changing things

Glurk, You wrote:
I think a lot of people, looking at the real world, would answer, yes, it clearly IS the same book, now in a different state. And yes, I CAN step into the same river twice. This is how people see and think about the real world.
I think this is a really interesting question, and I may be too new at functional programming myself to do it justice. I've been doing a lot of Haskell in a short time, so my experience is fairly intuitive and this email is part of the process of making it more conscious. Here's one way to structure the library: lend :: BookID -> Library -> Library actOn :: Request -> Library -> (Library, [Result]) actOn (Lend bookID) lib = (lend bookID lib, []) ... -- | A library is a transformation from Request events (e.g. 'lend') to Result events (e.g. overdue book email), -- implemented using lazy lists. library :: Library -> [Request] -> [Result] library lib0 reqs = concat $ snd $ mapAccumL (\lib req -> actOn req lib) lib0 reqs Now, are we mutating a library, or are we not? In spite of what the more Zen-oriented Haskellers might say, it seems to me we are. Call me uneducated but I can't really see what is actually wrong with the idea of mutation of state over time, where the abstraction is appropriate. Perhaps the point is that in Haskell, this is one of many ways of looking at it. Perhaps the essential difference here is that the library goes round in an "eddy" with an inflow of events influencing it as it goes round (and producing an outflow of Result events). This difference accounts for more than it seems to. (Trying to put my finger on it...) it's a much more restricted activity than mutating state outright, that means that if you change your code locally (e.g. re-write the I/O parts), your chance of breaking things is minimal to zero.
I guess that brings up another point - that eventually we are drawn into monads, which to me seems a bit unfortunate, because it seems that it leads us back to imperative programming with mutable state...
In many situations where I could use a state monad I don't (though I went through a stage of using them). My advice would be just write the code straight, and if the state management gets complex, a state monad may be warranted, or some better way may present itself. The great beauty of Haskell is that you have a lot of choices when a situation like this arises, and going for a state monad before you've examined the alternatives potentially restrict them. In the library example, if Library is a complicated data structure, you might want to have some lift functions that allow functions that affect only parts of the library to be lifted into a 'Library -> Library' type, e.g. liftDueDates :: (DueDates -> DueDates) -> Library -> Library liftDueDates f lib = lib { libDueDates = f (libDueDates lib) } Or to take the concept further ... There may be situations where you want to do some process where there are a number of parts of the library you want to work with, but conceptually some other way of viewing it makes sense. The "Haskell Way" would be to take the right parts of Library and create a new data structure that reflects the conception better. (In FP you often abstract things using data where you would use code in imperative languages.) If some output from that process has to contribute to a modified Library, then implement that as yet another transformation. Putting things back together like this is the extra work that Haskell makes you do - but the benefits outweigh the costs. So I'd say this: If you write your code in really plain Haskell, you'll tend towards a functional approach (e.g. why pass Library when it's better to transform library in some way first?). In that process, a state monad might suggest itself as a way of making the code more readable, but the thinking is still "plain Haskell". Then you're using a state monad with "right mindfulness" so to speak. Way of looking at it #2: If you are using a state monad because it's easier for you to understand the code that way, that's probably the wrong reason to use one. If you're using it because your straightforward Haskell is looking a bit unreadable, and the state monad tidies it up, then that's probably the right reason to use one. Way of looking at it #3: Your data needs to go through transformations from one form to another. In functional programming, you turn that process into a flow of data (or a sequence of transformations) from one form into another. If the flow loops, it starts to look a bit like mutating state, but in a way that's really only a surface appearance. One difference between state monads and mutating state in place (as done in OO languages) is that (in my experience) even with a state monad, the state mutation never really gets a chance to dominate the program. Haskellers have a saying "don't fear the monad - only IO is impure" that's applicable here. However, it's true that monads can lead to bad code, so they need to be used only where they're actually appropriate. I had an example of an excellent use for a monad recently: A parser where looking up an XML tag by its id string is abstracted as a continuation (so it can use IO if it wants) and it needs the ability to bail out if there's a parse error. A monad allowed me to write this code purely and cleanly. Another difference (state monad vs. mutating in place) is that a state monad can never lead to race conditions. Steve

Stephen Blackheath wrote:
Glurk wrote:
I think a lot of people, looking at the real world, would answer, yes, it clearly IS the same book, now in a different state. And yes, I CAN step into the same river twice. This is how people see and think about the real world.
[...] Here's one way to structure the library:
lend :: BookID -> Library -> Library
actOn :: Request -> Library -> (Library, [Result]) actOn (Lend bookID) lib = (lend bookID lib, []) ....
-- | A library is a transformation from Request events (e.g. 'lend') -- to Result events (e.g. overdue book email), -- implemented using lazy lists. library :: Library -> [Request] -> [Result] library lib0 reqs = concat $ snd $ mapAccumL (\lib req -> actOn req lib) lib0 reqs
Now, are we mutating a library, or are we not? In spite of what the more Zen-oriented Haskellers might say, it seems to me we are. Call me uneducated but I can't really see what is actually wrong with the idea of mutation of state over time, where the abstraction is appropriate. Perhaps the point is that in Haskell, this is one of many ways of looking at it.
I think it's entirely fine to interpret the above code snippet as a program that changes the current library. ("To change" can be read two ways: either to *ex*change the current library for a new one or to *mutate* the current library. I lean towards the former because it is closer to what Haskell actually does; but such fine print matters little.) However -- and this is the key point -- that doesn't mean that the model has to change or mutate anything. Put differently, I would attribute a lack of imagination to any notion that the model must be formulated in terms of mutable variables. In other words, whether the real world mutates things and whether we interpret it that way is quite irrelevant; the real question is whether the programming language we use to model the real world should include mutation (in the form of mutable variables). And the answer to that not only depends on how much the language caters to primordial intuition, as Glurk argues, but also on how fluent and expressive the language is and how it "interacts with itself". The premise of Haskell is that we pay a small translation cost from pure functions to an interpretation involving mutations, a cost that can be reduced to zero with proper training, but reap a big profit when it comes to manipulating and composing pure functions / programs. Regards, Heinrich Apfelmus -- http://apfelmus.nfshost.com
participants (2)
-
Heinrich Apfelmus
-
Stephen Blackheath [to Haskell-Beginners]