Mailing List Archive


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [tlug] recomendations for a functional language



On 2009-11-02 18:12 +0900 (Mon), Alan Busby wrote:

> On Mon, Nov 2, 2009 at 5:28 PM, Curt Sampson <cjs@example.com>wrote:
> >
> > Note, however, that Clojure, being a Common Lisp variant, is not really so
> > functional as even Scheme, not to mention missing very interesting features
> > such as pattern matching and a Hindley-Milner system.
> 
> Although it uses duck typing, and not Hindley-Milner....

Don't underestimate this difference. Moving from duck typing to a
good H-M type system is a larger jump than moving from something like
Java's "static" type system to Ruby's "dynamic" type system. Duck
typing is undoubtedly cool, but proving that your program cannot
ever fail in certain ways (bar compiler bugs or hardware failure) is
mind-blowing, and will change the way you look at everything. (It gave
me some interesting new insights into relational [database] theory, for
example.)

You haven't really lived until your program has produced the correct
result merely due to be compiled, and you realize the program doesn't
need to be run.

> I can't imagine Clojure not being considered "functional" simply due
> to all the things it took from Haskell such as it's use of immutable
> data structures, STM, and lack of state.

I'll address this in some detail, because this reveals some common
misunderstandings about Haskell--ones that took me over a year of
full-time programming in Haskell to start to really understand myself.

Let's start with the biggest one: "lack of state." Actually, in Haskell
you use a lot of state, probably as much as you think you're using in
any other programming language. (In other languages, you're actually
using quite a lot more.) Haskell is brilliant not because of lack of
state, but because of the very strict control of state: you can't use
state without explicitly declaring that you're using it and where and
how that state may propagate (assuming you understand how monads work
and "do"-notation syntactic sugar, of course).

The type system is essential to this level of control; I know of no
language without a powerful compile-time type system that can force
you to be so overt about your use of state. (I am willing to be shown
wrong on this point.) This extends into control structures; as it turns
out, control structures are all about control of state, and you get
some very interesting new ones that, while possible to replicate in
other languages (even Java), you're not likely ever to experience in a
language where the libraries are not built around them. Once you get
into this, it leads you back to the key realization that in typical
procedural or OO language, you're using a control structure for every
line of code you write:

    x = x + 1; printf("%d", x");

is actually using a control structure just as much as any code using
a while or for loop. And then you realize that you're dealing with
handling of state with that "this line executes before that one" control
structure.

Note well that we do frequently write imperative code in Haskell: that's
what the "do"-notation is all about, after all! And it's not just within
the IO monad; the ST monad is another example of writing imperative code
that executes imperatively; it's the control over where and when this
happens, and the ability to contain the side effects, that is the key
here.

And that, in fact, is the key to STM in Haskell as well. Haskell's STM
is nothing special in terms of STM itself; it's just doing what's been
available in C and other languages for a longer period of time. It's
the type system and its ability to contain and control side effects
and state that makes GHC's rather primitive implementation of STM work
better than much more sophisticated implementations that have been
written for C compilers.

As for the immutable data structures: it's a very nice thing that
Clojure has decided to go further in that direction in the standard
libraries than many other languages have. However, the difference
between Clojure with its library and Java with its immutable String
class is merely a matter of degree; it's not doing anything that OO
programmers haven't been doing for years when they realized at some
point in development of some application that a lot of the mutable
objects they were using should be value objects instead. That idea
wasn't taken from Haskell; value objects and persistent data structures
have been around since the dawn of Smalltalk and ML. That Haskell, in
general, forces you to use them (unless you want to do a bunch of extra
work in your implementation, as we've done with things like the ST
monad) is a side-effect of Haskell, but not really actually something
I'd tout as one of its great features, except inasmuch as it's part of
the "containment of state and side effects" feature (see above).

And that this is not really a "Haskell feature" shows: while the
Clojure standard library is great, I've heard, not only does Clojure
not guarantee the purity of functions (you never know when something
you're calling is going to do a setf!), but it cannot without breaking
the ability to use other JVM code; you must throw it all out the window
when you start to use the Java libraries, which rely heavily on mutable
state. Try to use a java.util.Date as a key in a map, and you're toast.

And I note that, in all of this, you've entirely left out the other
unrelated, yet quite important feature: pattern matching. This is a
basic language feature that I believe everybody should be aware of and
have used, whether or not they later chose a language that supports it.

> I find Clojure a happy middle ground between Haskell, Java,
> and Lisp; taking the best of each.

While I agree it's a good language, and a happy middle ground, it has
not taken the best of Haskell. Haskell's purity, type system, and
pervasive use of monads within the libraries are its true strengths, and
Clojure has none of these.

I do agree, by the way, that the macro facilities provide an extremely
powerful tool, but I don't feel that they're different to the degree
that something like a Hindley-Milner type system is. In other words:

> I'd argue that learning to use macros well, offers more educationally than
> the Hindley-Milner type system. Not that static typing isn't useful, but it
> doesn't open up new ways of thinking like the way code generation does.

Nope. Quite the other way around. But IMHO. I'll admit I don't have a
lot of experience writing Lisp macros. (Now, M4 macros, that's another
story. :-))

But keep in mind, a reasonably powerful type system *is* a programming
language in and of itself.

> If you're dismissing Clojure as another Common Lisp variant, I suspect
> you're missing the significant difference between the two, and should do a
> little more reading into what Clojure is before discussing it.

On review, Clojure is certainly further from Common Lisp than I'd
thought in my last post (despite being a Lisp-1, and not a Lisp-2),
especially in its emphasis on recursion rather than looping (though I've
not examined how well it really does when it comes to guaranteeing tail
call optimization, which is an important point when using recursion in a
language compiled to the JVM). But, while you may well even know Haskell
better than I know Clojure, I posit that Haskell is further from the sum
of all things you know about languages than Clojure is from the sum of
things I know about languages.

On 2009-11-02 11:23 +0100 (Mon), Attila Kinali wrote:

> You graded Common Lisp, Scheme and Haskell, without giving much
> information how you compared them. May i ask what you think the
> major differences of these languages are?

I think my comments above are applicable to most languages versus
Haskell. The big exception would be the ML family (of which Haskell
is more or less a member, really, albeit a rather odd cousin), and,
somewhat further away, Scala and Erlang.

For Common Lisp versus Scheme, I'd summarize the differences as:

  * the Lisp-1 versus Lisp-2 distinction (which can itself be
  over-summarized as whether function variables and non-function
  variables share a namespace, but upon research it turns out to be a
  lot more subtle than that),

  * that Lisp tends heavily towards iteration and Scheme towards
  recursion,

  * (previously) Lisp has macros, and Scheme doesn't, (now) Lisp has
  macros but Scheme has hygienic macros, but again, this distinction
  turns out to have tons of, er, "interesting" ramifications once you
  get into it, and makes you really think even more about whether you
  wanted macros or not;

  * Lisp does a lot, whereas Scheme is missing a lot, from certain very
  pragmatic points of view.

I can't really say you can go wrong learning either Common Lisp or
Scheme, quite frankly. They're a lot more different from each other than
Python or Ruby, but the greater differences all seem to be in areas
that are very troublesome and bear a lot of thinking about. Looking at
Clojure, actually, it seems as if it might be one of the best of the
Lispy languages to learn, if you're going to go that way.


On 2009-11-02 18:12 +0900 (Mon), Alan Busby wrote:

> Irregardless of quality, or old/bad design in many cases, it's very useful
> not to have to write an entire library by hand simply to deal with paypal,
> amazon, or any number of other systems.
> Not to mention the additional ease of deployment, like running code on
> Hadoop clusters on Amazon EC2.

Well, "writing an entire library by hand" is quite often not nearly
as hard as you'd think, if you're a half-way competent programmer. (I
have proof: I've done this, and I'd say I'm about half-way competent.
Hmm. Come to think of it, I may well be better at writing papers than
programming. I really should move to academia. :-))

And ease of deployment, well, this is one of the reasons I tend not to
use other people's libraries, frameworks, and so on.

But I said I wasn't going to get into this.

On 2009-11-02 19:01 +0900 (Mon), Stephen J. Turnbull wrote:

> Uh, you want to, uh, "clarify" that?  I mean, that clearly implies
> that you use libraries that deal with *money* "irregardless of
> quality, or old/bad design"!

That's reasonable. I once started into a ferocious argument about using
floating point for representing monetary values. There was, on some
forum somewhere, a poor soul with a PHP shopping cart who was worried
that it was using floating point to represent dollars, and perhaps
things like a 7% discount on the entire order wouldn't work out well.

Amid the calls for infinite-precision and clever fixed-point libraries,
I suggested that dealing with precision "properly" was going to be
pretty expensive, and he simply give, without question, a $10 discount
to any customer who complained for any reason about being out by a
penny or so. My theory was that this approach would be considerably
cheaper than even starting to think about switching from floating
point dollars. This was greeted with hoots of derision, and I have now
possibly destroyed any future possibility of consulting jobs involving
PHP shopping carts. (I may regret this in twenty years or so.)

Still, I am not the first. Many decades ago (though he probably is
wishing I wouldn't say that), the eminent (he likes it when I say that)
Stan Kelly-Bootle attempted to quench an early flame war, and his career
hasn't been the same since:

    Should array indices start at 0 or 1? My compromise of 0.5 was
    rejected without, I thought, proper consideration.

cjs
-- 
Curt Sampson       <cjs@example.com>        +81 90 7737 2974
           Functional programming in all senses of the word:
                   http://www.starling-software.com


Home | Main Index | Thread Index

Home Page Mailing List Linux and Japan TLUG Members Links