Chris said:
So, yes, I would consider pattern matching a form of dynamic dispatch
and thus something OO, albeit a newer and more interesting form than
OO itself (prior to FP languages) provided.
Ironically, FPLs predate OO by a decade IIRC.
(Lisp: sometime in the 50ies, Simula in the 60ies - I'm too lazy to
check the exact dates, but I'm pretty sure somebody knows them by heart *g*)
In fact, I don't consider HOF to be an FP innovation (at least not a
modern one), since HOF have been known and desirable for as long as I
can remember. In fact, one of the frustrations with Pascal (a pre-OO
language) was in trying to write HOFs--the argument passing powers
were more a hinderance than a help, which actually helped make C
popular, since it could do HOFs better.
The essential point is that you can construct functions at run-time.
This is not possible in Pascal or C, and marginally possible in C++.
The distinguishing characteristic of modern FP is type inference, a
better way of writing generic functions. This makes it possible to
write HOFs in a type-safe way.
HOFs could always be type-safe. The point of having type inference is
that it makes writing type-safe functions easier, because the type
signatures of a HOF can be quite mind-boggling. E.g. in a Pascal-style
syntax, the fold function would look like this:
function fold (function fun (A): B; initial: A; l: list of A): B
Now fold has a very simple signature; imagine a third-order function
that takes a function taking functions as parameters...
No, type inference doesn't make HOFs possible, but it does a large step
towards making them practical. (Parametric polymorphism is the other
important feature.)
Modern FP also inherits from vernerable FP (e.g. Lisp) the idea of
applicative programming, i.e. expressions without side-effects.
Ironically, applicativity was one of the first things that Lisp lost -
with the introduction of RPLACA and friends.
Though the applicative style never got completely lost. Both styles seem
to have coexisted through the years.
So, if you want to argue that type inference enaables solutions that
can't be expressed by inheritance, get on your soap-box and we will
listen, especially when you have evidence.
Soapboxed FPLers typically talk about parametric polymorphism (not type
inference) being the FP equivalent to ad-hoc polymorphism.
Actually most will readily acknowledge that OO-style polymorphism is
slightly more powerful, but they will also say that parametric
polymorphism avoids a whole slew of problems that are typical for
working with OO class hierarchies: dispatching asymmetries (rare but
requires premature design decisions if it occurs), dissemination of
logic across a class hierarchy (a common problem), no good way to
modularise a class from its subclasses (roughly: private is too
restrictive, protected/public exposes too many implementation details).
I have not enough experience with FPLs to say for sure that the argument
holds up in practice, but I have done enough OO class hierarchy design
to find the arguments plausible.
Similarly, if you can claim advantages from immutability, let us know.
Oh, that's really a FAQ, but anyway, here goes:
All problems that can be associated to pointer aliasing go away.
Instantly and without a trace.
There is no need to copy data structures. Since data will never change
when you're not watching, you can "copy" it by creating a new pointer to it.
In practice, FPLs with no mutable data structures don't even have the
concept of a "reference" - all assignment is by reference, since that's
a safe thing to do under immutability, so there's no need to have
by-value copying.
The absence of aliasing problems allow compilers to do a lot of
aggressive high-level optimisations. Every function can be recast as a
macro expansion and vice versa (try that with MIN() in C...), with no
worries about breaking things. (The worries for an FPL compiler writer
are the heuristics under which a function should be inlined...)
(Personally, I am most interested in monads, to see if they are
really a boon.
The topic is vastly overrated. They are just a specific Design Pattern.
Since this particular pattern is extremely abstract, many people waste
far too much time trying to understand the concept. Unfortunately,
nobody with enough knowledge and talent (and interest) has written a
good generally understandable explanation.
(The best approximation that I have come up with is "a monad is what's a
pipe in Unix, only type-safe", but (a) I'm not sure that this is fully
correct and (b) I'm pretty sure that this still isn't the full picture.)
Generally, you see GC adopted by OO and
tail-call-optimization will follow suit.) However, if you want to
criticize us for some slavish adherence to some rule, be careful, we
don't consider ourselves slaves. When I consider myself to be a
convert on FP (and I'm tettering as I find certain aspects of mutable
values really convenient), that doesn't mean I will stop considering
myself an OO programmer (nor even a structured programmer).
I had a somewhat sobering experience, and I don't consider myself an OO
programmer anymore.
The experience was this: I was part of the basic library standardisation
efforts for Eiffel. We tried to capture all relevant aspects of the
STRING class' in the form of preconditions and postconditions. I learned
two lessons there:
1) Doing assertions over containers without higher-order functions, by
direct recursion, is a nightmare and not really worth the effort. (We
had several cases where a function's body was easier to read than the
assertion. The problem would have gone away if we had been able to
directly write things like "forall characters in Current, this-and-that
holds".)
2) One of the dreams in the Eiffel community were that, given a set of
"precise enough" postconditions, the Eiffel compiler would be able to
generate the routine's body. It came as a shock to me to see that such a
postcondition would already be the function's body in an FPL - so why
wait until Eiffel acquires an "implementation inferencer" (which would
be *very* difficult to write since Eiffel doesn't have a formal
semantics and other problems that would make is very difficult to have
anything reliable for that).
3) Mutability makes it entirely impossible to nail down some essential
aspects of the workings of such a class. For example, if you write an
Eiffel loop like
loop
...
"foo".append(something)
...
end
you'll find that the STRING object will be reused, and grow with every
iteration. This isn't what one would expect, but it's perfectly in line
with mutable string semantics: "foo" is an instruction to the compiler
to create a (mutable) STRING object, and the language designer didn't
write it into the specs whether literal strings have to be recreated on
every loop iteration. (He even refused to add such a notice when asked
about the problem, because having to create the string object on every
iteration would be "inefficient".)
Well, of course I still can program in OO languages. It's just that I
don't see much value in inheritance anymore (but I gladly accept the
encapsulation that OO classes bring).
And, yes there are purists, people who are slavish devoted to solving
everything one-way. However, much of that purity is motivated by
practical considerations. For example, in a langue where everything
is an object, what one is really asking for is for everything to be
first-class. Something I think most FPers will appreciate. It's a
real pain when "integers" and "objects" have different rules and
one-or-the-other is not first-class.
Indeed. Though I'm not sure how that translates to FPL usage - I haven't
noticed anything "not quite functional" about FPLs.
So, please add closures and pattern matching to the next language I
use. If you give me bignums, I won't complain either. Just don't
take away inheritance if you can avoid it.
Inheritance makes type inference largely impossible.
If you have a call that says "foo.frobnicate", you don't know which of
the many classes in your system contains the "frobnicate" function
that's meant.
Well, if all "frobnicates" are in descendants of the same base class,
you can indeed do type inference - but what if they are in different
class hierarchies? From reading the program, neither the programmer nor
the compiler could infer what semantics is actually meant.
It really does solve certain problems in an elegant way that does
minimize code. And the penalty for using it isn't very high if one
finds the right hierarchy.
The same can be said for parametric polymorphism
The real question is: it parametric polymorphism enough? And if it isn't
and you have to circumvent the type system: are its other advantages
worth the restrictions?
However, I have one indirect evidence that it's indeed enough: All OO
languages that I know have some way to access RTTI, and to circumvent
the type system to work around the expressivity limitations of the type
system. Type casts (checked in better languages) are quite commonplace
and routinely applied by application programmers.
No such mechanism is available in a typical FPL. Well, some FPLs do have
it, but the docs lace such functions with such heavy warnings that
application programmers almost never use them. And it's very rare that a
programmer calls for such a thing here in comp.lang.functional.
To me, this indicates that parametric polymorphism is both powerful
enough and leads to less typing problems.
Regards,
Jo