
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