I'm learning Haskell this year (I try to learn a new language each
year), but I've hit somewhat of a roadblock. I can't figure out what
the advantages of a *purely* functional programming language like
Haskell is! With previous languages I learned, I could quickly pick up
the advantages: Python had pretty syntax and a REPL, OCaml has a real
static type system, Common Lisp has macros, read macros, and a REPL.
I think I understand the tradeoffs of programming in a functional style
-- the benefits are efficiency (through heavy memoization) and clarity
(from lack of side effects), and the disadvantages are heavy memory
usage (because there's no way to immediately recycle memory) and having
to write lots of "mutation communication code".
But it seems to me that you could get the benefits without having to
work in a purely functional language like Haskell. I also notice that I
don't mention lazy evaluation at all in the benefits or the
disadvantages, so there must be something that I'm missing.
I'm finding Haskell to be the hardest language I've learned, mostly
because of the alieness of pure functional programming. I keep finding
myself wanting to understand things imperatively.
Can you please explain what the advantages of using a *purely*
functional programming language instead of just coding in a functional
style is?
-- MJF
There's this very nice paper that deals with most of these issues:
http://www.math.chalmers.se/~rjmh/Papers/whyfp.pdf
As far as my personal experience goes, switching from PP to pure FP
feels pretty much like switching from trike to bike: you don't know
what it's like before you try, and no amount of coaching and/or
anticipation will give you the balance you need -- it only comes with
practice ;)
Cheers,
Dinko
> I'm learning Haskell this year (I try to learn a new language each
> year), but I've hit somewhat of a roadblock. I can't figure out what
> the advantages of a *purely* functional programming language like
> Haskell is! With previous languages I learned, I could quickly pick up
> the advantages: Python had pretty syntax and a REPL, OCaml has a real
> static type system, Common Lisp has macros, read macros, and a REPL.
I think the biggest benefit is what it can do for your programming even
in other, more imperative, languages. It's a different way of thinking
about a subset of programming problems. I don't think it's a reasonable
way of handling all problems.
> Can you please explain what the advantages of using a *purely*
> functional programming language instead of just coding in a functional
> style is?
I can only talk about what I personally have found useful. I've found
that a purely functional programming language is useful as a subset of a
complete solution, living on the outer nodes of the call graph of an
imperative program.
If you've got a subset of your problem that can easily be expressed as a
function of some immutable data, then a functional approach is
warranted. If this subset is a significant fraction of the total, then
it starts to make sense to write a chunk of your application in a
functional language.
-- Barry
>But it seems to me that you could get the benefits without having to
>work in a purely functional language like Haskell. I also notice that I
>don't mention lazy evaluation at all in the benefits or the
>disadvantages, so there must be something that I'm missing.
>
>I'm finding Haskell to be the hardest language I've learned, mostly
>because of the alieness of pure functional programming. I keep finding
>myself wanting to understand things imperatively.
Here's one way to think about lazy evaluation and functional
programming (FP).
In my search for powerful programming languages, I am basically looking
for the ability to be as abstract as possible when describing the
solution to a problem. Obviously, I have to specify things
precisely enough for the solution to actually work. I can't just say
"sort the list", I must actually write the code, or at least call a
specific library function.
The flip side is that I don't want to specify any details which
aren't relavant to the solution. In most cases, I don't want to worry
about 32-bit int vs. bigint, for example. I just want the tools
to figure out which is best (space, speed, precision, efficiency).
Good programming languages have lots of goodies for all kinds of
data abstractions. They let you think in terms of objects, rather
than some bag of bits.
In all mainstream languages, you must be intimately concerned
about time/sequence. You are always making decisions about when to
compute this value, and if it must occur before or after this other
computation.
And in fact, you _must_ make these kinds of decisions, even if the two
computations do not depend on each other at all, and that one may
preceed the other without any consequence.
There will always be dependancies on sequence, there is no avoiding
that. But only with lazy evaluation (which requires FP) can we
start to abstract out time/sequence. And let the tools handle the
details where possible.
James Graves
Another paradigm that lets you abstract this out is dataflow. Examples that
come to mind are LabVIEW, Prograph (which I ate up back in the early '90's),
and simply hooking up UNIX tools. There are some similar features to
dataflow and FP languages, obviously, but one of the major ones is that you
stop worrying about time: you just specify what Op A needs in terms of
inputs to proceed. If the inputs are available, then Op A can do its thing,
but it doesn't have to, not until its output is urgently demanded in the
bigger scheme of things (i.e the output of Op A is needed by Op D, and
everybody else is waiting on Op D).
I do not think you stop worrying about sequencing - it's just that you don't
write any code for superfluous sequencing.
With Haskell (I'll use that since it is the FP language I am learning right
now) I see the correspondence of lazy evaluation to dataflow. I wrote a list
comprehension (one of my first) to calculate (1 +/- x)^n, with x**2 < 1, and
was absurdly pleased. :-) In that case, and also with any Prograph I did, or
shell scripts I've written, I don't think I have lost any sense of
sequence - in fact I am specifiying sequence fairly explicitly as part of
the declaration of the solution - but I am losing the control structures
that feed or drive the data; the system takes care of that.
To answer the original poster's question, and also to agree with Barry
Kelly, I am learning Haskell in order to think differently about solving
problems. I started out with FORTRAN IV, rapidly moved into FORTRAN 77, VAX
assembler, C64 BASIC and K&R C, and Perl 4. I didn't even encounter OOP
until Perl 5 emerged, and then actually thought about it when a CD of JDK
1.0.2 passed my way. That caused me to look at Smalltalk and C++. Prograph
in about 1990-91 entirely made me think differently at an earlier stage.
Somewhat more recently, XSLT conditioned me a bit for Haskell, and writing a
program for doing declensions and conjugations in Latin, using Prolog,
exposed me to logic programming - which again has many similarities with
dataflow and FP. After many many years of stultifying exposure to Java
(which core language I actually don't dislike so much) I decided to learn
Eiffel to get a different picture of OOP.
What have I had to actually use on the job? A hell of a lot less than what I
have decided to learn. But I don't think it was any surprise that I was
introducing hardcore Windows-Java programmers left, right and centre to
regexps a considerable amount of time before Sun decided to incorporate
RE's, nor was it any surprise when I taught people that you think about SQL
the way you think about XSLT; most caught on to that. When I downloaded
Cygwin on one job - something that nobody else would have thought to do
(none knew UNIX, for starters) - and then automated some tasks that were
proving a real PITA on Win 2000, using ActiveState Perl (and nobody else
knew Perl, Python, Tcl, REXX, anything), I'd like to think that a few people
actually started thinking outside the box.
Haskell actually has me a bit screwed up right now because, as one says
about Perl, TIMTOWTDI. I am in the phase of trying to do everything with
ultra-elegant list comprehensions, and this is probably not optimal. In any
case, learning the language has had me dragging out my old texts on data
structures, sorting, searching, university math etc. In the course of my
career, almost no formal job has ever had me think about basic data
structures - linked lists, arrays, hashes, queues, stacks, trees etc - or
algorithms. Right now, learning Haskell *is* making me think about that.
That can only bear fruit when I apply the languages I have to use on the
job.
AHS
Pattern Matches even in Haskell are dependent on the sequence...
...first pattern matches is the rule, not only in non-pure FPLs.
You will not get the perfect world with FPLs...
(but a much better ;-) but even impure FPLs are close to this!)
Ciao,
Oliver
It's funny you should mention dataflow languages; I was just thinking
about how dataflow languages seem to have the benefits of purely
functional languages without abandoning side effects.
I was thinking about the following example: You have a simulation of a
bunch of creatures. Some creatures are predators. Some creatures are
prey. The prey will run away from all predators that are closer to it
than distance D. A predator will wander around randomly until it sees a
prey within distance E, in which case it'll follow that prey until it
either touches the prey (and eats it) or the prey get's more than
distance F away from it.
Using Haskell, I'd expect the world to be modeled as a stream of a list
of streams of creature states. The creature state for a predator would
need to hold a stream of the prey it is stalking. Streams of lists of
streams of structures containing streams scare me. If this was coded in
a dataflow style, I could see where I'd want to use dataflow, and where
I *wouldn't*.
I guess it's the *purely* functional part that scares me. I see that
making any sort of side effect (which I do all the time in simulations)
extremely difficult. Do I have any sort of point here? Not really; I'm
just rambling.
-- MJF
I liked the approach. I come from a physics/engineering background. I
therefore like what makes sense and what works. I see nothing wrong with
side-effects, and sometimes, maybe often, I want them.
> I was thinking about the following example: You have a simulation of a
> bunch of creatures. Some creatures are predators. Some creatures are
> prey. The prey will run away from all predators that are closer to it
> than distance D. A predator will wander around randomly until it sees a
> prey within distance E, in which case it'll follow that prey until it
> either touches the prey (and eats it) or the prey get's more than distance
> F away from it.
>
> Using Haskell, I'd expect the world to be modeled as a stream of a list of
> streams of creature states. The creature state for a predator would need
> to hold a stream of the prey it is stalking. Streams of lists of streams
> of structures containing streams scare me. If this was coded in a
> dataflow style, I could see where I'd want to use dataflow, and where I
> *wouldn't*.
>
> I guess it's the *purely* functional part that scares me. I see that
> making any sort of side effect (which I do all the time in simulations)
> extremely difficult. Do I have any sort of point here? Not really; I'm
> just rambling.
You have a good point, actually. You mention that you do simulations, so
I'll assume that using another language you'd solve the above problem fairly
handily. But in fact it's not a simple problem (in point of fact it's a very
complex problem), so perhaps your difficulty in figuring out how to
implement this in a pure FP language is more a statement of this: you've
made certain assumptions relevant to Other Language A in order to handle the
abstract problem, and now you cannot see how to readily translate the
_assumption-conditioned_ solution under Language A to FP language B, where
in fact it should return to a consideration of how to treat the original
abstract problem with the mindset of FP language B.
I'm no expert, but if someone gave me that problem and Haskell, I'd model it
like this. I have N things, prey or predator - two types. Hence, a list of N
things of 2 different types (since lists are homogeneous, I'd have a
Creature type, perhaps). Since I am a tyro at Haskell, I'd see no reason to
model the concept of position in anything other than 1-D in integers (*), so
distance would be a simple integer subtraction. Distances D, E and F would
pertain to the interaction between Type Prey and Type Predator, with an
argument of distance besides, and a simple output of movement (or lack of
being output to indicate consumption).
That would be first cut. I wouldn't model speeds of movement, reaction times
of prey or predator, cooperative behaviour, anything else. I would just do
the above super-simplified problem. State of each creature would be Position
and what it is; deciding what it reacts to is a filter on distance in 1-D
from neighbours; and outcome is another list of creatures with new
Positions. Special cases - like 2 Predators equidistant from a Prey - I'd
just handle with an arbitrary "Oh shit" rule: react to the first Predator.
Point being - I have now solved a reasonably sophisticated first
approximation. That in itself has made me think about how I would solve this
in any language. In fact, were I to approach this in Perl or C++ or Java or
Eiffel, I'd be done relatively quickly, and I'm not convinced I'd have a
better simulation. Whereas, in Haskell, I am really starting to think about
the real problem. In particular, I would not yet have identified any need to
have side-effects (read maintaining state elsewhere).
AHS
* Re using a 1D assumption to model locations of Prey and Predator. Not as
useless as one might think, as a simplification of 2D or 3D geometry of
Creature locations. Unless I were going to 2nd order assumptions to model
more sophisticated geometrical requirements - fish camouflage in the water
column, approach from the rear, diving attacks by raptors - I wouldn't much
worry about extra refinements first off.
I was assuming the modeling was interactively stepped in real-time.
I'm not worried about converting the timer or the renderer to
pure-functional. After all, that is impossible! My concern is
converting over the stateful interaction between predators and preys,
which also depend somewhat on object identity.
-- MJF
One thing which I don't think I described well was the stateful behavior
I intended for predators. In C++ I would model a predator as:
struct Predator : Creature {
Creature* currentPrey; //< HOW DO YOU MODEL THIS?
virtual void update() {
if( this->currentPrey != NULL ) {
//stalk prey
if( distance( *this, *currentPrey ) > F ) {
this->currentPrey = NULL;
}
} else {
//random wander
this->currentPrey = findPreyLessThanDistance( *this, E );
}
}
};
It seems to me that FP uses streams where imperative languages use side
effects. There's a straightforward translation from the Haskell
implementation of the fibonacci sequence:
fib1 = 1 : 1 : zipWith (+) fib1 (tail fib1)
fib x = fib1 !! x
to the following C implementation:
int fib( int x ) {
if( x == 0 ) {
return 1;
} else if( x == 1 ) {
return 1;
} else {
int cur = 1;
int prev1 = 1;
int prev2;
for( int it = 2; it <= x; ++it ) {
prev1 = cur
prev2 = prev1;
cur = newPrev1 + newPrev2;
}
return cur;
}
}
I don't care that the Haskell version is much shorter (that's a seperate
issue); the important thing is that where C's fib used side effects,
Haskell's fib used streams. I get the feeling from what I've read both
online and in "The Haskell School of Expression" that this is pretty
standard -- Haskell uses streams where side effects would be used in an
imperical language.
-- MJF
>Pattern Matches even in Haskell are dependent on the sequence...
>...first pattern matches is the rule, not only in non-pure FPLs.
That's true, in a sense. The sequence is actualy how you assign
priority to pattern matching. We could easily come up with some other
syntax which allows the programmer to assign priority, and then be able
to list the patterns in any order.
I meant sequence of actual operations. We can, with lazy evaluation,
not be concerned about the sequence of operations in some circumstances.
But every program will be concerned about time/sequence to some extent.
For example, in a video game, it is necessary to calculate the position
of each object X times per second. Only after the calculation is
complete will the screen be updated.
>You will not get the perfect world with FPLs...
>(but a much better ;-) but even impure FPLs are close to this!)
I'm still striving for a perfect world though. I haven't given up on
that yet. :-)
James Graves
<snip>
> One thing which I don't think I described well was the stateful
> behavior I intended for predators. In C++ I would model a predator
> as:
>
> struct Predator : Creature {
> Creature* currentPrey; //< HOW DO YOU MODEL THIS?
>
> virtual void update() {
> if( this->currentPrey != NULL ) {
> //stalk prey
>
> if( distance( *this, *currentPrey ) > F ) {
> this->currentPrey = NULL;
> }
> } else {
> //random wander
>
> this->currentPrey = findPreyLessThanDistance( *this, E );
> }
> }
> };
Just my two bits, but one way to make this more like a functional
approach would be to make Predator immutable and have the update
function return a new Predator. The update function would create a new
Predator object, passing it all the state information it needs, such as
the current prey, location, etc. This could lead to a proliferation of
Predator objects, but on the plus side, if you keep track of those
instances, you have a history that you can easily retrace.
Here's an interesting presentation that talks in part about using
objects in this way:
http://www.ccs.neu.edu/home/matthias/Presentations/ecoop2004.pdf
> It seems to me that FP uses streams where imperative languages use
> side effects.
Hmm, with the above approach, maybe we have a stream of objects?
Right but now it's gotten messy. currentPrey has to take on the value
of a prey *alreday in the simulation* and be *updated with the
simulation*. I can only see doing this by storing a stream in
currentPrey in the Haskell version. And now I have to make sure that I
update that stream in sync with the stream in the simulation (not too
hard, unless I want to "pause" predators).
But what's the advantage of doing all this extra structure?
-- MJF
<snip>
>> Just my two bits, but one way to make this more like a functional
>> approach would be to make Predator immutable and have the update
>> function return a new Predator. The update function would create a
>> new Predator object, passing it all the state information it needs,
>> such as the current prey, location, etc. This could lead to a
>> proliferation of Predator objects, but on the plus side, if you keep
>> track of those instances, you have a history that you can easily
>> retrace.
>
> Right but now it's gotten messy. currentPrey has to take on the value
> of a prey *alreday in the simulation* and be *updated with the
> simulation*. I can only see doing this by storing a stream in
> currentPrey in the Haskell version. And now I have to make sure that
> I update that stream in sync with the stream in the simulation (not
> too hard, unless I want to "pause" predators).
I think I see what you're saying, but not sure. If all of the objects
are immutable, you have to make sure that a Predator's currentPrey
pointer (if not null) points to the latest version of a Creature
somewhere in the simulation. If each update creates a new version, it
becomes important that all relationships between objects are in sync,
i.e. an object's relationship with another object must be with the
latest version of that object to be valid. Is that close to what you're
saying?
The simulation as a whole would probably have this responsibility. Using
your example, the method...
findPreyLessThanDistance( *this, E );
...would need global access to all of the objects in the simulation.
Probably a good place to put the responsibility for keeping the
simulation in sync.
> But what's the advantage of doing all this extra structure?
Well, the history of your simulation would persist. So you could easily
run it backwards, or look at the simulation at a certain point in time
to examine the state of the simulation.
I come from the imperative side having grown up with C and C++ and now
use C#. I've found the functional approach fascinating, however, and
have tried to implement some of its approaches in my own mostly
imperative code. I've found that that limiting the mutability of an
object makes the code more robust. It gives me the same kind of
advantages as avoiding global data. However, I don't shoot for a purely
functional approach. For one thing, I'm not using a functional language
(though C# gets a little closer with each version). And another thing is
that I find it useful to have side-effects but to limit when and where
they occur.
You simply don't model this separately. This particular field is given
a life of its own in C++ -- you can change it while the containing
object "stays the same" -- but not in Haskell. When you want to model
a sequence of events where objects (or the world) change state, the
natural thing to do in Haskell is to supply a stream of changing object
(or world) states.
I'd suggest that FRP (http://www.haskell.org/frp/) would be quite
relevant to what you're doing (I didn't read this thread through very
carefully, but I suspect someone may have already pointed this out.)
[...]
> It seems to me that FP uses streams where imperative languages use side
> effects. There's a straightforward translation from the Haskell
> implementation of the fibonacci sequence:
>
> fib1 = 1 : 1 : zipWith (+) fib1 (tail fib1)
> fib x = fib1 !! x
>
> to the following C implementation:
>
> int fib( int x ) {
> if( x == 0 ) {
> return 1;
> } else if( x == 1 ) {
> return 1;
> } else {
> int cur = 1;
> int prev1 = 1;
> int prev2;
>
> for( int it = 2; it <= x; ++it ) {
> prev1 = cur
> prev2 = prev1;
>
> cur = newPrev1 + newPrev2;
> }
>
> return cur;
> }
> }
That's an oversimplification. Granted, if you don't expose fib1,
deforestation will kick-in and actually transform your Haskell program
into something like your C code. Stress the fact that the C program is
equivalent to fib and fib1 put together -- it's a bit more trouble to
implement fib1 alone, where side effects are beside the point.
> I don't care that the Haskell version is much shorter (that's a seperate
> issue); the important thing is that where C's fib used side effects,
> Haskell's fib used streams.
That's more like it :) John Hughes' paper talks about how lazy
evaluation helps separate generation from selection/termination.
Cheers,
Dinko
Yes, exactly. It seems to me that once you start having identity-based
relationships between objects, and not class/value based relationships,
pure functional programming becomes a lot *more* coding. I need to
transform every object that potentially cares about identity into
streams while making sure to update each instance in sync. That seems
to me to be a lot of potentially error prone code that will cause weird
results if it contains a bug.
>> But what's the advantage of doing all this extra structure?
>
> Well, the history of your simulation would persist. So you could easily
> run it backwards, or look at the simulation at a certain point in time
> to examine the state of the simulation.
Assuming garbage collection has not yet kicked in and removed all the
previous state. I could easily see the garbage collector running once a
second with all the new state being created.
> I come from the imperative side having grown up with C and C++ and now
> use C#. I've found the functional approach fascinating, however, and
> have tried to implement some of its approaches in my own mostly
> imperative code. I've found that that limiting the mutability of an
> object makes the code more robust. It gives me the same kind of
> advantages as avoiding global data. However, I don't shoot for a purely
> functional approach. For one thing, I'm not using a functional language
> (though C# gets a little closer with each version). And another thing is
> that I find it useful to have side-effects but to limit when and where
> they occur.
I don't disagree with this -- I find that functional code is easier to
reason about *where appropriate*. I even annotate my code with that
information.
I suffix variable names with "Accum" for accumulators and "It" for
iterators; all other variables are single-assignment. Noun functions
are idempotent and verb functions are not. I'd say a good 90% of my
variables and 40% of my functions are functional from an outside
perspective. It's that remaining 10% of the variables that causes 60%
of the functions to be imperative; I view eliminating that as hard work
that needs a real, tangible benefit. Lazy functions and garbage
collection are useful, but I don't see them as the benefit; they can be
used in im-pure functional languages.
-- MJF
Thanks for the link. I'll be sure to read up on some of this.
-- MJF
Interestingly nobody seems to have mentioned the history of the subject.
That is we have in 1936 Turing coming with the idea of `computation' as
being what can be done with a Turing Machine. At the same time we have
Church coming up with Lambda Calculus. The two are shown to be equivalent
in computing. State based machines came into being and the associated
languages with them, first assembler and then Fortran, through to PASCAL/C.
C++ and the others followed. On the alternative side you have Backus and
his famous speech about bloated languages with more and more features,
which sparked off research in FP languages in the 1970s. Haskell came out
as a standard version of all the different syntactic flavours of lazy
functional programming languages in 1988, and so 16 years later you are
learning it.
But in short - the reason why FP are `better` than state based languages is
that it is more mathematically orientated, simply because in mathematics
variables don't change their values once they have been assigned, where as
in state based languages variables have different values in different
places and different times and so it make reasoning about what the program
does difficult.
Nim.