Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

[Haskell-beginners] Re: Applicability of FP unchanging things to real world changing things

0 views
Skip to first unread message

Stephen Blackheath [to Haskell-Beginners]

unread,
Dec 24, 2009, 7:59:19 AM12/24/09
to begi...@haskell.org, stre...@hotmail.com
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
_______________________________________________
Beginners mailing list
Begi...@haskell.org
http://www.haskell.org/mailman/listinfo/beginners

Heinrich Apfelmus

unread,
Dec 25, 2009, 7:08:04 AM12/25/09
to begi...@haskell.org
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

0 new messages