In 100 words or less, why is Clojure better than Common Lisp?
Have you switched to Clojure? If so, from what?
It has more kinds of parentheses. And symbols have to prove themselves
worthy before they are interned.
> In 100 words or less, why is Clojure better than Common Lisp?
That constraint I can't deal with. I only ``do'' 25 words or less.
And anyway, in this world, by the time your ad execs throw together a monstrous
100 word sound bite, the competition has eaten your lunch.
> Have you switched to Clojure? If so, from what?
NewLisp, mostly. In fact, I switched from NewLisp to Clojure about six times
just today alone! Well, no four. One of those times I went to Scheme first,
then Clojure, and another time I was actually using Common Lisp and /thought/
it was Clojure, until I misspelled some names. The misspellings scrolled off
the screen, and so I didn't know what to unintern to get back to a clean state.
I ended up rebooting the machine.
> On 2009-02-10, Kenneth Tilton <kent...@gmail.com> wrote:
>> In 25 words or less, why is Clojure better than Common Lisp?
>
> It has more kinds of parentheses. And symbols have to prove themselves
> worthy before they are interned.
This is why it's worse.
>> In 100 words or less, why is Clojure better than Common Lisp?
>
> That constraint I can't deal with. I only ``do'' 25 words or less.
And 25 is not less than 100 anymore?
> And anyway, in this world, by the time your ad execs throw together a monstrous
> 100 word sound bite, the competition has eaten your lunch.
--
__Pascal Bourguignon__
I guess I've switched, from Common Lisp, mostly because I changed jobs
last November and I couldn't take my CL code base with me, so I was
able to examine Clojure from a fresh perspective.
I'm exploring Clojure for my simulation work. The biggest plus for me
in my domain is Clojure's concurrency model: immutable data
structures, agents, STM, and NO USER LOCKS!. This makes it very easy
to spread model calculations across cores without having to protect
against improper concurrent data sharing either with hard to develop/
debug locks or by "convention". Even though Clojure has no built-in
distributed support, it'll eventually get one and until then, it
appears that one can use any of the mechanisms available through the
JVM.
The second big thing for me is the Java interoperability, as my domain
needs access to lots of different math and there's lots of math
libraries for Java that are accessible through Clojure's very nice
Java interop facilities. Additionally, it's very easy to tell Clojure
what the types are of data items (like various CL declarations,
although easier to use) which speed things up. CL's numeric tower is
more featureful, but Clojure's is certainly adequate. There's no
native complex number support, although you can do that in the Java
layer. I haven't yet needed to use complex numbers in my work so
that's not a big deal for me but I have seen some postings of math
types who miss native complex numbers. There's no problem with
Clojure functions being the callbacks for Java code, as Clojure
functions implement all the right Java interfaces to make that work.
The HotSpot JVM seems speedy enough for me and it has a JIT so things
that run a while automatically speed up. For some micro benchmarks in
the areas that I care about, Clojure compares well with SBCL once the
JIT has had a chance to optimize things. The last time I had done any
serious work in Java was in 1997 so I was very pleasantly surprised at
how far JVM performance has progressed.
I initially thought I was going to miss CLOS because my prior code
base heavily depended on CLOS, especially method combination to help
make a lot of boilerplate disappear (in addition to removing the rest
of the boilerplate with a very high-level abstract model definition
language implemented with macros on steroids), but the way maps
(hashmaps), multi-methods, meta-data, and ad-hoc hierarchies work
together mostly make up for the lack of CLOS.
The big step for me here was getting past "it doesn't have CLOS, how
can I model essentially object-oriented simulation models without
something like CLOS?" to "with what's available in Clojure, what's the
Clojure-idiomatic way of creating the model building abstractions I
need?" Once I got past that hang-up, I was able to proceed quite
nicely and am happy with the result. My user-level model building
code will be as nice, concise, and high-level as my CL version was
(this is all work in progress).
The other things I miss in Clojure in addition to the above mentioned
method combination are symbol macros, macrolet, and LOOP (!), but
there are ways around most of these things so their ommision aren't
too painful. For day-to-day programming, LOOP is probably the thing I
miss the most, as any looping has to be done through recursion.
There's a language construct to make tail-recursive calls efficient on
the TCO-less JVM, but I do miss some of the more powerful features of
LOOP (yes, I'm LOOP-evil). However, the Clojure community is very
active so I expect that over time these will be filled in with
libraries.
When I was doing my initial investigation last December, what really
cinched it for me was watching Rich's Clojure for Lisp Programmers
videos and the concurrency video. They are very good and should
answer any questions about whether Clojure is suitable for any
particular task. The concurrency video is particularly good and goes
into technical depth discussing Clojure's concurrency support, the
design decisions Rich faced and why he made certain choices, and an in-
depth code walkthrough of an ant-colony food gathering simulation
using agents. Very cool stuff. Even though the video wasn't
specifically about Java interoperability, he does show building up a
graphical UI to show the ants speeding around in the REPL. Very, very
cool stuff.
My opinion after watching these videos is that Rich has *very* good
design taste. Starting with the goals he wanted, in my opinion, he's
made the right decision every time. It's obvious from the videos that
he has a deep command of programming language theory, which has led
him to choose a lot of semantics and implementation techniques that
fit together well, instead of that "groping in the dark" feeling you
get from say, certain languages.
In summary, I'm not going to say that Clojure is better or worse than
Common Lisp. For my particular domain, I can do some things easier
than I can do in the Common Lisp implementations I have available to
me (an important qualifier!) so it's useful for me. Your mileage may
and will vary.
Oh, I should add that I've purchased the beta of Stuart Halloway's
"Programming Clojure" book from Pragmatic Programmer's and it covers
the language nicely. I was able to breeze through the book in several
hours and came away with a good command of the language. Keep in mind
that it's more geared towards a non-Lisp reader than a CL expert.
Glenn Ehrlich
<snip great response>
Thx! You gave me ideas for a lot of questions. New survey soon (all of
which you have already answered).
kt
(I think the rest are for anyone who will be using Clojure)
If Java access was a big factor, why not ABCL or AllegroCL?
What do you miss from Common Lisp (or whatever the "from" language was).
Other _______
> Kaz Kylheku <kkyl...@gmail.com> writes:
>
>> On 2009-02-10, Kenneth Tilton <kent...@gmail.com> wrote:
>>> In 25 words or less, why is Clojure better than Common Lisp?
>>
>> It has more kinds of parentheses. And symbols have to prove themselves
>> worthy before they are interned.
>
> This is why it's worse.
>
>
>>> In 100 words or less, why is Clojure better than Common Lisp?
>>
>> That constraint I can't deal with. I only ``do'' 25 words or less.
>
> And 25 is not less than 100 anymore?
<http://en.wikipedia.org/wiki/Sarcasm>
--
Raffael Cavallaro, Ph.D.
> What is your Clojure Status?
is clojure about to vault into the tilton pantheon?!
> - What is Clojure?
> - Never looked at it.
> - Looked but did not like because ___
> - Will use for some work because ___ but not all because ___ but
> continue to use ___ for ___ because ____.
will use for some work but not all because of:
cross platform java libs, especially cross platform gui libs
integration with emacs/slime
active enthusiastic user community and much contributed code
mikel evins likes it!
built in ease of threading due to immutablility of clojure data
structures (i.e., pure functional semantics).
possiblility of easy web deployment via applets (though not currently
high on my list)
continue to use CCL because of excellent mac os x integration.
> - Will now use instead of ___ because ____
> - Other ____
>
>
> (I think the rest are for anyone who will be using Clojure)
>
> If Java access was a big factor, why not ABCL or AllegroCL?
>
> What do you miss from Common Lisp (or whatever the "from" language was).
maturity of the language. code from six months ago often doesn't work
because breaking changes are still being made to the language.
>
>
> Other _______
--
Raffael Cavallaro, Ph.D.
No, just curious. Thx for the survey response. Mikel is indeed a big plus.
kt
I'm syncing to the daily builds of clojure, and doing some minor
benchmarks. I'm trying hard to reach the speed of Common Lisp or Java
(I'm running Clojure with the -server option).
Overall, the speed ain't bad, it's about 10x faster than perl, python,
ruby, but x10-x20 slower than most of the Common Lisp Compilers (SBCL,
CMUCL, LispWorks, ACL).
I'm using Lisp for heavy calculations, so I can't really use it, until I
learn to optimize it better.
My base benchmark was the pnpoly.lisp code (point-in-poly).
Other things that I'm missing from Common Lisp are some form of
"apropos" and some way of disassembling the functions - I'm doing this
regurarly with Common Lisp in order to learn the peculiarities of the
various implementations, and gather knowledge of best practices myself
(off course there are papers by Fateman, and others about single and
double floating points).
Another strange peculiarity is the tagging - sometimes you have to do
(#^Float number), sometimes (number (float 10.0)) - it seems not
consistent, but granted this is not where the language is shining.
I think it's not the language right now for my needs, but my become, as
soon as I start explore web development more and more.
The JAR file is rather small, compared to the ABCL (not fair comparison,
I know different things - but still to give you perspective).
It definitely looks useful, and can teach you how to write even more
functional code (still trying to prevent myself from imperative thinking).
spreading across cores makes sense if it speed ups things, right?
even if there are no apparent locks, STM etc., etc. might have its
overhead. and it might be possible that Clojure running on all 8 cores
works actually slower than SBCL running on single one.
so, all these features are sort of "cool", but totally meaningless
without proper benchmarking data. (if we're speaking about CPU-bound
applications.)
i did not like it because it is not a Common Lisp!
plus, at time i was looking at it, it threw errors in Java backtrace format,
that is, you've misplaced paren, and it throws ton of bullshit at you.
very unpleasant. (it seems they've aldready fixed this)
In general one can suggest to give type hints to speed up things.
For a little test function that Rainer Joswig gave here in c.l.l some
weeks ago I got from x15 more runtime than sbcl to half the runtime of
sbcl with a few type hints.
But sometimes those won’t help. Currently the bignum lib from Java seems
to be pretty slow.
For Java 7 some drastic improvements are visible on the horizon:
http://markmail.org/message/7conncsespvrlazn
Another general suggestion is to run things in parallel.
This makes of course only sense if your algorithms are parallelizable.
If they are not, and if you are using Clojure datastructures (such as
sets, vectors, hashmaps, ...) instead of Javas it can also be in some
cases a disadvantage. The Clojure versions simply have to do more work
under the hood. These datastructures are not only threadsafe, but also
concurrency ready, which is even more expensive.
> Other things that I'm missing from Common Lisp are some form of
> "apropos"
So find-doc did not give you satisfying results?
It can also take a regex...
> and some way of disassembling the functions
There are many Java tools that can support you.
Did you try jad to decompile .class files into .java files?
(in that sense Java is the Assembler for the JVM :))
But you can also use bytecode viewer.
> Another strange peculiarity is the tagging - sometimes you have to do
> (#^Float number), sometimes (number (float 10.0)) - it seems not
> consistent, but granted this is not where the language is shining.
As you can see, float is just an ordinary function, but does implicitly
give a type hint:
http://code.google.com/p/clojure/source/browse/trunk/src/clj/clojure/core.clj#1813
And btw, (number (float 10.0)) produces an error, as the function number
does not exist. Did you mean number? maybe?
Type hints can be given very consistent with the reader macro #^
as in #^TYPE
> I think it's not the language right now for my needs, but my become, as
> soon as I start explore web development more and more.
I hope you did not base this on the one result of your poly test case.
Although you know of course much better your needs. If that benchmark is
what you are basically doing all the time, then it’s very understandable.
André
--
Lisp is not dead. It’s just the URL that has changed:
http://clojure.org/
The question at this point could be why this is worse.
Honestly, I see only advantages here.
The [] and {} give some hold to the eye, which I find nice.
One can of course argue that it is less consistent, and that a „real
Lisper” will only want to use (). But let’s not forget that () them-
self are reader macros. And if we want to argue with consistency in
mind, then those people should not make use of other reader macros in
CL as well, such as ' #' ` , ,@
I however see that a few handselected reader macros are a great thing.
They should exist for the most commonly used tasks.
Lists are mostly used in Clojure to write code :)
Vectors have taken more or less the place of the list.
One can „cons” elements to it (the function really is conj) and traverse
it with the same performance as lists. But random access is done in near
constant time. Also reversing a vector is much faster, though this is
less often needed, as conj adds elements at the end of the vector, not
to the front (as with lists).
Passing around small hashmaps is so simple.
(some-function {:a 10, :b 20}) vs
(let ((temp (make-hash-table)))
(setf (gethash :a temp) 10
(gethash :b temp) 20)
(some-function temp))
And the other thing that Kaz said about interned symbols also makes sense.
For example Hans Hübner writes in his blog:
„Clojure wants to be a Lisp, but it explicitly does not try to be
backwards compatible. This opened a rather large design space to Rich
Hickey, and some of the choices he made really do make sense. He
specifies a reader, yet his reader does not intern symbols. That is a
big win, as it allows the reader to actually work with arbitary Clojure
source files. In Common Lisp, one needs to re-implement a full reader
which does not intern symbols if one wants to read Common Lisp source
files. This is kind of ironic, as the "Code is Data" mantra that we keep
repeating does not really reflect what is possible in practice.”
Soure: http://netzhansa.blogspot.com/2008/10/trying-clojure.html
> I hope you did not base this on the one result of your poly test case.
> Although you know of course much better your needs. If that benchmark is
> what you are basically doing all the time, then it’s very understandable.
>
>
> André
Thanks Andre!
No I'm not going away from it - rather I'm staying - i like the
interactivity of it, the funny thing about it running on JVM (for good
or bad, I can get this thing to run on almost any platform, because it
uses JVM).
Also Rich Hickey seems to know his stuff, and only following his
creation, is enough to grasp powerful ideas, even if I may not use his
language at the end professionally.
Definitely I'm staying, and I'm going to dabble into it even more...
For example ARC did not leave me with that impression - to me arc
sounded like Common Lisp in Rap Lyrics/Slogan... And fact is Paul Graham
is still my favourite lisp person (I've read his stuff). Maybe I'm
missing something from ARC too - but that's another topic.
> Pascal J. Bourguignon schrieb:
>> Kaz Kylheku <kkyl...@gmail.com> writes:
>>
>>> On 2009-02-10, Kenneth Tilton <kent...@gmail.com> wrote:
>>>> In 25 words or less, why is Clojure better than Common Lisp?
>>> It has more kinds of parentheses. And symbols have to prove themselves
>>> worthy before they are interned.
>> This is why it's worse.
>
> The question at this point could be why this is worse.
> Honestly, I see only advantages here.
> The [] and {} give some hold to the eye, which I find nice.
> One can of course argue that it is less consistent, and that a „real
> Lisper” will only want to use (). But let’s not forget that () them-
> self are reader macros. And if we want to argue with consistency in
> mind, then those people should not make use of other reader macros in
> CL as well, such as ' #' ` , ,@
Let's say that it's a personal taste. (And indeed, I use (function x)
rather than #'x, and sometimes (eg for pedagogical reasons), I use
(quote x) instead of 'x.
Reducing the set of special characters used to write code let you
write it faster. Keying in English is fast, because you only use
letters, and a very small number of punctuation, compared to keying in
popular programming languages. If you resist the temptation of using
a lot of reader macros in lisp, then you can type lisp code as fast as
English text.
Also, it would be nice if the language didn't use all the characters,
and left some for user's reader macros. In Common Lisp, {}, [] , !?
and a few others are reserved to the user. (Of course, with unicode
this is less of a problem).
> Passing around small hashmaps is so simple.
> (some-function {:a 10, :b 20}) vs
>
> (let ((temp (make-hash-table)))
> (setf (gethash :a temp) 10
> (gethash :b temp) 20)
> (some-function temp))
This is crazy. Nobody write such a form!
(htable :a 10 :b 20)
--> #S(HASH-TABLE :TEST EXT:FASTHASH-EQL (:B . 20) (:A . 10))
>
>
> And the other thing that Kaz said about interned symbols also makes sense.
> For example Hans Hübner writes in his blog:
> „Clojure wants to be a Lisp, but it explicitly does not try to be
> backwards compatible. This opened a rather large design space to Rich
> Hickey, and some of the choices he made really do make sense. He
> specifies a reader, yet his reader does not intern symbols. That is a
> big win, as it allows the reader to actually work with arbitary Clojure
> source files. In Common Lisp, one needs to re-implement a full reader
> which does not intern symbols if one wants to read Common Lisp source
> files. This is kind of ironic, as the "Code is Data" mantra that we keep
> repeating does not really reflect what is possible in practice.”
> Soure: http://netzhansa.blogspot.com/2008/10/trying-clojure.html
We only need to add a CDR for a READTABLE-PARSE-TOKEN hook to be able
to read uninterned sources without reimplementing the reader.
But even with the current standard, the point is that reader macros
are not to be abused for lisp programs. It's better in lisp programs
or embeded DSL to use normal functions or macros such as (htable :a 10
:b 20) than to use a reader macro such as {:a 10 :b 20}.
Reader macros are designed to build user level DSL.
If you need to read a DSL source without "interning" it, then you
should use a normal parser.
--
__Pascal Bourguignon__
I beg to point out that some folks can touch-type the non-letter
keys...
and although clearly farther away from home position and therefore
slower,
each non-letter used in a non-letter-rich syntax will generally
represent
many letters of lisp symbol. non-letter-rich syntax can be an
effective
compression for 1) reading, 2) thinking, and yes also 3) typing, if
one
touch-types the non-letters.
That said, the whole reason I like and use the CL type of Lisp syntax
is
because my preferences agrees with yours on:
> Also, it would be nice if the language didn't use all the characters,
> and left some for user's reader macros. In Common Lisp, {}, [] , !?
> and a few others are reserved to the user. (Of course, with unicode
> this is less of a problem).
This is not an issue for clojure:
from: <http://clojure.org/reader>
"The read table is currently not accessible to user programs."
Of course many common lisp users will think this more a bug than a feature...
--
Raffael Cavallaro, Ph.D.
I'm using it to learn the Java libraries without having to use Java. I
don't think I'll be lucky enough to find another Lisp job soon:
finding IT jobs in Belgium with no Dutch is hard enough. So I'm
preparing for the worst...
> If Java access was a big factor, why not ABCL or AllegroCL?
Clojure in non-proprietary and has lots of interesting features built-
in, in particular, concurrency, abstract datatypes (collections,
mappings), lazy evaluation.
> What do you miss from Common Lisp (or whatever the "from" language was).
I haven't been using Clojure for long enough to miss much of Common
Lisp.
...
Another interesting approach to leveraging Java libraries is Jnil
(http://common-lisp.net/project/jnil/), which actually translates them
into Common Lisp. The maintainer could really do with some support.
cheers,
Tom
The word „better” is nothing objective, so people can disagree on what I
personally rate as better.
My list of things that I like in Clojure better than CL
(in no particular order):
1. It basically supports one major paradigm/style of programming,
the functional one. CL too does support it, but CL also supports
other styles, and it is even very idiomatic to do imperative
programming in CL.
People will disagree if offering a smaller number of programming
style is better or not, but I like it better. It means that most
developers will come up with more comparable/compatible solutions.
Whatever one may think about the Java programming language, it’s a
fact that it offers a very large set of libraries. This is due to
the limits which are built in into the language. There is basically
one way (few ways) to do it, instead of many ways, as in CL.
So, I prefer to have one major style in Clojure. It happens to be
the one I preffered since many years in CL too, the functional one.
2. Concurrency ready. I see basically 3 or 4 languages out of the
many hundreds in use, that are seriously capable of doing concurrent
programming: Clojure, Erlang, Haskell, and also going there, F#.
While it is not impossible to do it in other languages, Clojure does
make it really easy. It’s in some sense the argument that was in
favour of CL in the past 15-20 years: „Yeah, mathematically speaking
it is possible in other languages too. In principle one can solve any
problem in Assembler. It’s just that it is much easier in Lisp, as it
comes with powerful abstractions”.
Clojure has gone the evolutionary step into the right direction (in
my opinion), by providing standard tools for concurrency. Other langs
like CL or Python can also do functional programming. But the problem
is that they also offer an alternative way. As long that exists the
devs have to program by agreement, and they need to be very strict.
Currently not many languages have a good implementation of a STM with
Multiversion Concurrency Control. I think for F# one is currently
being developed. But this really should come with the lang itself,
as this would be an integral part. If this is an addon lib, then
other implementations may arise, which can (no doubt) also have ad-
vantages, but brings back incompatibility.
3. Modernized syntax. It makes totally sense to me (you might disagree
of course) to have some basic syntax support for the most common
operations. The CL committee had the same idea in mind and intro-
duced ' #' #+ #- ` , ,@ which, again, makes very much sense
to me. To make access to datastructures other than lists easier,
Clojure went a step further, and provides reader macros for vectors
(which can replace lists to a great part), hashmaps and sets, and
of course strings (and regular expressions).
I now hate it so much when I go back to CL that everything is so
chatty. It does not buy me anything that I have to bloat up my code
for typical tasks.
Clojures datastructures are fully integrated into the macro system.
4. Lazyness. In many places Clojure is very lazy :)
This is great. Not only does that improve performance greatly, no, it
also allows new idioms and a more elegant programming style. It’s
well integrated.
5. It runs on the JVM. Today many languages do run in a VM (like CL,
Python, C#, ...). Why develop your own when you can have the most
mature one?
Chosing the JVM brings also tons of advantages:
a) Operating system does not matter so much anymore. CLs are
available for most plattforms too, though this does not always
help. No recompilation needed. I can switch back between the OSes
and continue coding. The devs of the same team have the choice of
their OS. The employer would not have to force us anymore to use
something we don’t like. The plattform in the end is always the
JVM.
b) Giant lib. Want a server for RESTful applications? Integration
with SAP? The JVM can not do magic, but it really haves very
much to offer, library wise.
c) Will help to create much more Lisp jobs.
I personally left two times my home city to get a Lisp job. One
time I even left my country for it. As Java is the most popular
programming language it is more likely to find interesting pro-
jects in cities of my choice. Of course I don’t want to do Java
programming, but there will always be Java companies (probably
the not too big ones) that will accept you in their team as a
Clojure developer. True, it potentially can bring in some diffi-
culties if you are the only one who can read/write Clojure code.
But some companies will weight that against the 2x - 20x boost in
productivity that Lisp can bring over Java. Clojure brings me into
the position that I suddenly can negotiate something with the
company.
So, maybe a bit more than 100 words, but here you go.
The points that I listed will not be the ones that everyone else will
also find „better”. I however do.
> Have you switched to Clojure? If so, from what?
Yes, switched from CL to Clojure.
And if it is not necessary, I would not want to go back.
But sure, if Clojure is no option then CL would always be my next
choice.
I respect your opinion, however, I would like to mention two things:
1) The connection to Java can be seen by some folks as very healthy.
2) When you argue about matureness and evolution, then Clojure could
be interesting. The language is much more simple than Lisp. And
some core parts of a Lisp were already provided by the JVM. For
example the GC, the Exception system and some tenthousand ready to
use methods. In that regard, Clojure can be seen as even more mature
than CL implementations. As Java is the most popular programming
language it gets on a daily basis more man hours of testing than
all CLs got in the past years. Billions were invested by the biggest
IT companies into the JVM, and it went through an evolutionary
process in which it got very mature.
Speaking of evolution: Clojure learned from Lisp. So it is not a
new language that started from scratch. No, it inherited the DNA
of its parents. It took the efforts of research in the past 50
years and continued to evolve from that point. So, it is the next
step in evolution of Lisp.
It is: I use it daily for professional work, but also nearly every day
for personal programming fun.
I use Clojure now instead of CL mostly, and will port my code.
> (I think the rest are for anyone who will be using Clojure)
>
> If Java access was a big factor, why not ABCL or AllegroCL?
Because those are CL implementations which come with clos.
I like clos more than the object system of Java, but this is just my
opinion. A fact however is, that those object models are not compatible.
It is very ugly to work with Java from within CL.
So, if access to Java would be a big factor, then one should definitly
use a language which was designed with that idea in mind.
> What do you miss from Common Lisp (or whatever the "from" language was).
From CL I miss in principle nothing.
I am glad that Clojure has no object system, but instead goes the path
of functional programming.
What I miss though are features of specific CL implementations.
For example SBCLs type inference engine.
> And
> some core parts of a Lisp were already provided by the JVM. For
> example the GC, the Exception system and some tenthousand ready to
> use methods. In that regard, Clojure can be seen as even more mature
> than CL implementations.
Not really. This argues for the maturity of the *platform* on which
clojure is built (i.e., the jvm). Clojure itself is clearly still not
mature. There's a thread still running on how to fix mod:
user=> (mod -3 3)
3
So clojure, the language, not the platform it is built on, is still not
nearly as mature as common lisp.
--
Raffael Cavallaro, Ph.D.
oh yes, it is very healthy (for a functional language!) to have "recur"
special
operator instead of normal tail recursion because of JVM deficiency.
AT> In that regard, Clojure can be seen as even more mature
AT> than CL implementations.
platform maturity has nothing to do with language implementation maturity.
i have here a build labeled "2008-09-16", just few months ago error handling
was totally broken and weird as it was spitting java backtraces:
user=> (())(()))
java.lang.ClassCastException: clojure.lang.PersistentList$EmptyList cannot
be cast to clojure.lang.IFn
java.lang.ClassCastException: clojure.lang.PersistentList$EmptyList cannot
be cast to clojure.lang.IFn
at user.eval__2290.invoke(Unknown Source)
at clojure.lang.Compiler.eval(Compiler.java:3891)
at clojure.lang.Repl.main(Repl.java:75)
when considerable portions of implementation get reorganized in short
periods of time,
that is *not* called "mature"
I've looked at it, but I have a bunch of code in CL that I'm working
on and see no compelling reason to switch.
If I should decide I need my app to run on the JVM at some point --
which is possible -- I would consider Clojure, but I would also
consider Scala.
-- Scott
It’s true, it’s healthy to have recur.
I agree that it is not nice that the JVM does not have tail recursion
yet. But in practice that is not a real issue, as recur is available.
Let’s look at this unreadable mess in CL:
(DEFUN Y (F)
( (LAMBDA (G) #'(LAMBDA (H) (FUNCALL (FUNCALL F (FUNCALL G G)) H)))
#'(LAMBDA (G) #'(LAMBDA (H) (FUNCALL (FUNCALL F (FUNCALL G G)) H)))))
(FUNCALL (Y #'(LAMBDA (FN)
#'(LAMBDA (X)
(IF (ZEROP X) 0 (+ X (FUNCALL FN (- X 1)))))))
200)
==> 20100
we can do this in Clojure:
((fn [n] (loop [x n, res 0]
(if (zero? x) res (recur (dec x) (+ x res)))))
12345678)
No Y-Combinator needed, much shorter, much more pleasant for the eye.
Plus: this call works.
When I gave the argument of 2000 in clisp on Windows it crashed.
Stack overflow I guess.
Or another example:
(defun fibonacci (n)
(labels ((fibo-helper (x result)
(if (zerop x)
result
(fibo-helper (1- x) (* x result)))))
(fibo-helper n 1)))
would be in Clojure (without recur):
(defn fibonacci [n]
((fn fibo-helper [x result]
(if (zero? x)
result
(fibo-helper (dec x) (* x result))))
n 1))
The labels is not needed, as fn can be given a name (fn in Clojure is
CLs LAMBDA). Anyway, this is not stack-safe right now, as the JVM does
not support tail call optimazation (though Jon mentioned that the team
of the OpenJDK is working on that).
Now in Clojure we simply replace the call of fibo-helper with recur:
(defn fibonacci [n]
((fn fibo-helper [x result]
(if (zero? x)
result
(recur (dec x) (* x result))))
n 1))
That’s the only change. It is still following the idiom that is needed
in all programming languages for tail recursive calls.
This would also allow us to eliminate the function name we gave to fn.
This makes it again shorter. And if we want to, we can put in a loop:
(defn fibonacci [n]
(loop [x n result 1]
(if (zero? x)
result
(recur (dec x) (* x result)))))
And although I am doing CL since 6 years and Clojure just 4 months or
so, this looks cleaner in my opinion than the CL version.
> AT> In that regard, Clojure can be seen as even more mature
> AT> than CL implementations.
>
> platform maturity has nothing to do with language implementation maturity.
> i have here a build labeled "2008-09-16", just few months ago error handling
> was totally broken and weird as it was spitting java backtraces:
>
> user=> (())(()))
> java.lang.ClassCastException: clojure.lang.PersistentList$EmptyList cannot
> be cast to clojure.lang.IFn
> java.lang.ClassCastException: clojure.lang.PersistentList$EmptyList cannot
> be cast to clojure.lang.IFn
> at user.eval__2290.invoke(Unknown Source)
> at clojure.lang.Compiler.eval(Compiler.java:3891)
> at clojure.lang.Repl.main(Repl.java:75)
>
> when considerable portions of implementation get reorganized in short
> periods of time,
> that is *not* called "mature"
Yes, I understand your position. And also thanks to Raffael, he is also
right.
In fact, Clojure did not reach 1.0 status, so yes, breaking changes can
still occur. But most of the time Rich adds more (very useful)
abstractions and tools to the language, instead of making breaking changes.
However, I also like to look at this from a more practical side.
Clojure is usable now. One can step with debuggers through the code
line by line, one can profile it, compile it into doubleclickable
.jar files (like .exe under Windows) or simply work in Emacs+Slime.
Nearly all parts of the language itself work perfectly. But when
writing real world applications one constantly can be in need of
libs, that already do parts of what you want to do.
Lisps like Allegro, Lispworks or sbcl are being worked on every
day. It’s similar to what happens to Clojure.
It is a bit unfair to make the comparison of what changes in the
core language. CLs is already defined and can not be changed.
Clojure is still open, and Rich can decide to add more to it.
So of course, this will result in more changes in the core when
compared to what happens to CLs.
I worked professionally with Allegro and also Lispworks.
In both I or my workmates discovered bugs in their libs, like
database stuff.
This is much more unlikely to happen when using Clojure, as it
simply offers some ten thousand more functions which still need
to be implemented in CL first, and which were tested some orders
of magnitude more intense than their CL equivalents.
And one could argue if it can be seen as a bug as well, if some
libs are simply not available (like a server for RESTful apps)
in CL. Although Jon writes a lot of provocative material here,
and sometimes explicitly enjoys to bring in some trolling, he
also mentioned very true things, such as that some CL libs have
like 80 users. Not 80k, but a mere 80. Development on important
stuff does not happen as much as I wish (are cursors already
available in sql libs?).
Clojure is actively and intensly used by some hundred people.
We test every new feature, and honestly, I did not stumble upon
lot’s of bugs. Rich fixes them so fast, it’s really nice.
So, if I see the whole variety of things that are available when
programming in Clojure, then only a tiny fraction of it is not
mature.
It’s true that there are some more bugs, but also check out what
SBCL changes:
http://www.sbcl.org/all-news.html
Just scroll down this list. SBCL exists since years, and not one
main developer is working on it, but 5-25.
When I look at this list I see not interesting additions to the
main language and libraries. Tons of bug fixes for core stuff.
Maybe it’s okay to call SBCL not mature, and only see the main
two commercial CLs (Allegro and Lispworks) this way.
I don’t know what they are doing, but they still do bugfixes
constantly. And although they had 15+ more years of time than
Clojure, they still need to fix core stuff. And in their libs
they have also several bugs that my companies uncorvered.
As much I understand the POV that Clojure is not mature, because
of the good reasons you and Raffaello gave, I see it differently
and would call it already mature.
It already offers right now basically everything that is needed
for professional and commercial development. What mostly happens
is that even better abstractions are added every few days.
For me it’s extremly difficult to go back doing CL, because I miss
so much. Kenny asked in this thread also what I miss from CL.
I have to think long to come up with maybe a few points.
But the other direction.. that is the real problem.
> we can do this in Clojure:
> ((fn [n] (loop [x n, res 0]
> (if (zero? x) res (recur (dec x) (+ x res)))))
> 12345678)
we can do this in common lisp:
((lambda (n) (loop for x from n downto 0 summing x)) 12345678)
and when we do it in Clozure Common Lisp (dx86cl64) it runs 10 times as
fast as your clojure code on the same machine.
--
Raffael Cavallaro, Ph.D.
We were talking about recur and its advantages. The issue was not about
performance.
It’s likely that Clozure CL or also SBCL will run much faster, as
Clojure would use boxed numbers, which have to be unboxed first and then
boxed again.
One would have to give type hints, as in:
(loop [r (long 0) n (long 12345678)] (if (zero? n) r (recur (+ r n) (dec
n))))
Now that runs probably at very comparable speed.
Anyway, if I want to outperform your Clozure Cl code above by a factor
of 150 in Clojure, then this would do:
(defn sum [n] (+ (/ n 2) (/ (* n n) 2)))
> We were talking about recur and its advantages. The issue was not about
> performance.
[snip]
> (loop [r (long 0) n (long 12345678)] (if (zero? n) r (recur (+ r n) (dec n))))
The point is that there is no advantage to recur. It's a wart
necessitated by the jvm's lack of tail call optimization.
In any conforming scheme it's just a plain old recursive call. In any
conforming common lisp it's just a simple loop. And in most decent
common lisps, as long as you're not optimizing for debug to keep the
stack frames, if you really want to wear the scheme recursive hair
shirt, you can do it recursively too:
(labels ((loop (r n) (if (zerop n) r (loop (+ r n) (1- n)))))
(loop 0 12345678))
but of course a real loop, not recursion, is the common lisp norm:
(loop for i upto 12345678 summing i)
--
Raffael Cavallaro, Ph.D.
>
> but of course a real loop, not recursion, is the common lisp norm:
>
> (loop for i upto 12345678 summing i)
>
>
Depend what you call norm. (let ((j 0)) (dotimes (i 12345678 j) (incf j
i))) seems less offensive to loop haters. Some swear Lisp should be purely
functional. So pehaps series? ;) The style wars will never end.. I would
guess there are as many styles as Lispers. The developers at Clojure
dislike that you can mix and match styles. To me it is one of the things
that attracts me to the language.
--------------
John Thingstad
> On 2009-02-13 14:41:14 -0500, André Thieme
> <address.good.un...@justmail.de> said:
>
> > we can do this in Clojure:
> > ((fn [n] (loop [x n, res 0]
> > (if (zero? x) res (recur (dec x) (+ x res)))))
> > 12345678)
>
> we can do this in common lisp:
>
> ((lambda (n) (loop for x from n downto 0 summing x)) 12345678)
Reduce:
for n:=1:12345678 sum n;
76207888812681
In Reduce it should be
for n := 1:12345678 sum n;
In CL it is
(loop for n from 1 below 12345678 sum n)
Longer, but same thing.
Cheers
--
Marco
> but of course a real loop, not recursion, is the common lisp norm:
>
> (loop for i upto 12345678 summing i)
Ruby:
sum=0;(1..12345678).each{|n| sum+=n}
(/ (* 12345678 12345679) 2)
Longer, but faster :)
Cheater :)
cheers
bobi
AT> It's true, it's healthy to have recur.
AT> I agree that it is not nice that the JVM does not have tail recursion
AT> yet. But in practice that is not a real issue, as recur is available.
this reminds me a soviet anecdote: when there is no meat in the butcher's
shop (it was often the case)
they place an announcement at the shop's door: "there is no necessity in a
meat today".
are you as brainwashed as soviet's?
recursion is not limited to the cases when you just call current function in
a tail-call fashion. in functional
programming often you need to call function in an alternating fashion -- foo
calls bar, bar calls foo back
etc. and recur simply cannot do this. it can only emulate simple loops.
educate yourself: http://en.wikipedia.org/wiki/Mutual_recursion
AT> Let's look at this unreadable mess in CL:
AT> (DEFUN Y (F)
AT> ( (LAMBDA (G) #'(LAMBDA (H) (FUNCALL (FUNCALL F (FUNCALL G G)) H)))
AT> #'(LAMBDA (G) #'(LAMBDA (H) (FUNCALL (FUNCALL F (FUNCALL G G))
AT> H)))))
this is a piece of cheap propaganda, i can't believe you're writing this
seriously.
you've tried hard to make it looks like an unreadable mess, and now you
claim it is unreadable mess.
whoa. NOBODY WRITES CODE LIKE THIS.
if you need to do recursive call of a local function in CL, use labels.
AT> (FUNCALL (Y #'(LAMBDA (FN)
AT> #'(LAMBDA (X)
AT> (IF (ZEROP X) 0 (+ X (FUNCALL FN (- X 1)))))))
AT> 200)
AT> ==> 20100
it is not tail recursive, btw. tail recursive version with labels:
(labels ((sum (x acc) (if (zerop x)
acc
(sum (- x 1) (+ acc x)))))
(sum 200 0))
AT> we can do this in Clojure:
AT> ((fn [n] (loop [x n, res 0]
AT> (if (zero? x) res (recur (dec x) (+ x res)))))
AT> 12345678)
AT> No Y-Combinator needed, much shorter, much more pleasant for the eye.
no Y combinator is needed in Common Lisp either, and code is quite similar
when you
remove it. you might argue that anonimous functions/loops are somehow
better, but i
find named ones more readable.
what would be even "more pleasant for the eye": (loop for i from 1 to n
summing i)
AT> Plus: this call works.
AT> Stack overflow I guess.
sure it does, because it was not tail calls. you should admit you know very
little
about functional programming, as you do not know even basics.
AT> When I gave the argument of 2000 in clisp on Windows it crashed.
CLISP does not optimize tail calls in debug mode, iirc.
in SBCL it works fine with in default mode:
CL-USER> (labels ((sum (x acc) (if (zerop x)
acc
(sum (- x 1) (+ acc x)))))
(sum 20000000 0))
200000010000000
AT> Or another example:
AT> (defun fibonacci (n)
AT> (labels ((fibo-helper (x result)
AT> (if (zerop x)
AT> result
AT> (fibo-helper (1- x) (* x result)))))
AT> (fibo-helper n 1)))
wow, so now you know about labels and tail recursion. good for you.
it is not fibonacci, it is factorial, btw.
AT> This would also allow us to eliminate the function name we gave to fn.
you say it like it is some unique feature of Clojure. with a simple macro
you can make recur like in Clojure in CL. but you cannot add support for
tail calls in Clojure in any way, until they fix it in JVM.
AT> This makes it again shorter. And if we want to, we can put in a loop:
AT> (defn fibonacci [n]
AT> (loop [x n result 1]
AT> (if (zero? x)
AT> result
AT> (recur (dec x) (* x result)))))
AT> And although I am doing CL since 6 years and Clojure just 4 months or
AT> so, this looks cleaner in my opinion than the CL version.
than version you've wrote in CL. i would write it like this:
(defun factorial (n)
(loop with p = 1
for i from 1 to n
do (setf p (* p i))
finally (return p)))
IMHO that's cleaner. or if i absolutely must use recursion:
(defun factorial (n &optional (result 1))
(if (zerop n)
result
(factorial (- n 1) (* n result))))
it is even shorter than your Clojure thing.
AT> However, I also like to look at this from a more practical side.
AT> Clojure is usable now.
many people find PHP usable and mature -- it does not bother them
that it lacks important features or that it often gets incompatible changes.
SCNR *g*
I see your point, however, I personally disagree.
First of all, I would like to mention that recur also has no disadvantages.
That is not enough to introduce something into a language. Otherwise one
could add recur1, recur2, ... recur24637, ...
They all would have no disadvantages (assuming they work exactly like
recur).
But the fact that the JVM currently does not support tail call opt.
under the hood is a very plausible reason to add it.
Besides that, recur does in fact have three advantages:
1. it allows anon functions to call recurse, they can call themselves
2. it blocks you from accidently not doing tail calls, because the
compiler can check if recur really is in the tail position
3. as a minor advantage I want to add, that it is easy to search in code
for occurrences of “recur”, or if you spot it somewhere while flying
over your sources, you immediately see “Ah, here we have a recursive
call”.
So even when the JVM will support one day tail call optimization, recur
will not go.
Jon Harrop indicated that this (adding TCO) is happening at the moment
for the OpenJDK, and as Sun works on their own functional programming
language “Fortress” I suppose they will also be interested in adding it.
I will definitly continue to use recur. What I will stop to use are
trampolines.
> but of course a real loop, not recursion, is the common lisp norm:
>
> (loop for i upto 12345678 summing i)
Yes, loop is nice. In principle it’s what I would do in CL as well,
although for this specific case I would really prefer to directly
calculate that number via (defn sum [n] (+ (/ n 2) (/ (* n n) 2))).
In Clojure I could do:
(apply + (range 12345678)))
or use reduce instead of apply.
> recursion is not limited to the cases when you just call current function in
> a tail-call fashion. in functional
> programming often you need to call function in an alternating fashion -- foo
> calls bar, bar calls foo back
> etc. and recur simply cannot do this. it can only emulate simple loops.
>
> educate yourself: http://en.wikipedia.org/wiki/Mutual_recursion
For that case Clojure offers trampolines.
Yes, one can argue that trampolines are a specific idiom which one needs
to learn. It’s true. But let us not forget that tail recursion itself
already is such an idiom. It is an optimization hack.
No programming system currently allows you to write recursion as in a
way that is closest to math.
We always need to apply the design pattern “Tail recursion”.
So there already is something complex that one needs to do. Now what is
different in Clojure is, that you don’t make the calll
(fun 1 2 3) but instead
#(fun 1 2 3) when you use trampolines, and it is stack-safe again.
It’s not that hard to remember. But yes, it is one key stroke more complex.
Still I would stop using trampolines as soon the JVM will introduce that
kind of optimization. I will however continue to use recur, for the
typical recursion, which makes like 99% of all recursion I usually use.
> AT> Let's look at this unreadable mess in CL:
>
> AT> (DEFUN Y (F)
> AT> ( (LAMBDA (G) #'(LAMBDA (H) (FUNCALL (FUNCALL F (FUNCALL G G)) H)))
> AT> #'(LAMBDA (G) #'(LAMBDA (H) (FUNCALL (FUNCALL F (FUNCALL G G))
> AT> H)))))
>
> this is a piece of cheap propaganda, i can't believe you're writing this
> seriously.
> you've tried hard to make it looks like an unreadable mess, and now you
> claim it is unreadable mess.
> whoa. NOBODY WRITES CODE LIKE THIS.
Well, it’s from Kent Pitman.
http://www.nhplace.com/kent/Papers/Technical-Issues.html
> if you need to do recursive call of a local function in CL, use labels.
>
> AT> (FUNCALL (Y #'(LAMBDA (FN)
> AT> #'(LAMBDA (X)
> AT> (IF (ZEROP X) 0 (+ X (FUNCALL FN (- X 1)))))))
> AT> 200)
> AT> ==> 20100
>
> it is not tail recursive, btw. tail recursive version with labels:
>
> (labels ((sum (x acc) (if (zerop x)
> acc
> (sum (- x 1) (+ acc x)))))
> (sum 200 0))
Where is the anonymous function here?
recur allows you anon functions to call themself.
> what would be even "more pleasant for the eye": (loop for i from 1 to n
> summing i)
Yes true, for that specific task it is much nicer.
I like it nearly as much as (apply + (range n)).
> AT> Plus: this call works.
> AT> Stack overflow I guess.
>
> sure it does, because it was not tail calls. you should admit you know very
> little about functional programming, as you do not know even basics.
I see no reason to admit that at this point, as I was fully aware about
this. What I was doing was talking about anon functions calling themselves.
> AT> Or another example:
> AT> (defun fibonacci (n)
> AT> (labels ((fibo-helper (x result)
> AT> (if (zerop x)
> AT> result
> AT> (fibo-helper (1- x) (* x result)))))
> AT> (fibo-helper n 1)))
>
> wow, so now you know about labels and tail recursion. good for you.
> it is not fibonacci, it is factorial, btw.
Yes right, how embarrassing :-/
> AT> This would also allow us to eliminate the function name we gave to fn.
>
> you say it like it is some unique feature of Clojure. with a simple macro
> you can make recur like in Clojure in CL. but you cannot add support for
> tail calls in Clojure in any way, until they fix it in JVM.
Let’s say you have a Lisp with no support for TCO.
Then you would also be forced to wait until your vendor fixes it.
Or you have an open source solution and do it yourself (as it currently
happens with the OpenJDK).
And of course one can add recur also in CL. Everything that Clojure has
can be done in CL and vice versa.
> AT> This makes it again shorter. And if we want to, we can put in a loop:
> AT> (defn fibonacci [n]
> AT> (loop [x n result 1]
> AT> (if (zero? x)
> AT> result
> AT> (recur (dec x) (* x result)))))
>
> AT> And although I am doing CL since 6 years and Clojure just 4 months or
> AT> so, this looks cleaner in my opinion than the CL version.
>
> than version you've wrote in CL. i would write it like this:
>
> (defun factorial (n)
> (loop with p = 1
> for i from 1 to n
> do (setf p (* p i))
> finally (return p)))
>
> IMHO that's cleaner.
After some months of functional programming I don’t like code with setf
anymore. But your solution is working perfectly, and it’s only my taste,
not a technical issue.
In Clojure I would probably do:
(defn factorial [n] (apply * (range 1 (inc n))))
or if I want the set of all factorials, then
(def fibs (lazy-cat [0 1] (map + fibs (drop 1 fibs))))
This is my favourite. No function needed, just a variable bound to an
infinite lazy sequence.
This also has the nice advantage of being automatically memoized.
> or if i absolutely must use recursion:
>
> (defun factorial (n &optional (result 1))
> (if (zerop n)
> result
> (factorial (- n 1) (* n result))))
>
> it is even shorter than your Clojure thing.
It’s true, but I don’t like this solution at all, because it introduces
an optional argument which can’t be used at the user site.
Peter Seibel calls this a leaky abstraction.
For hobby programming it is okay, but a clean solution would be
(defun factorial (n)
(labels ((helper (n result)
(if (zerop n)
result
(helper (1- n) (* result n)))))
(helper n 1)))
There was a joke about NASA(or RFSA) spending a lot of money for pens
that could write in zero gravity. When they announced their
masterpiece reporters asked them why they don't use pencils?
cheers
bobi
cheers
bobi
> First of all, I would like to mention that recur also has no disadvantages.
It can't be used for mutual recursion. Common Lisp's labels can;
scheme's letrec can. Clojure needs a separate piece of
work-around-the-jvm syntax for mutual recursion.
--
Raffael Cavallaro, Ph.D.
if you compare Clojure without recur to Clojure with recur, obviously recur
will be seen as an advantage. but if you compare Clojure to CL, you will
find recur quite limited, and thus having recur INSTEAD of recursion is a
disadvantage.
AT> or if you spot it somewhere while flying
AT> over your sources, you immediately see “Ah, here we have a
recursive
AT> call”.
it is highly subjective, it seems to me that recur is less readable because
it
makes harder to see what is recur'ed -- one might think it calls outer
function
while it calls inner. typically explicit is better than implicit.
btw, we might compare "recur" to a compiler that will automatically
detects such cases of recursion and optimizes them accordingly.
(i believe most compilers do this, even C ones). then one might argue
that recur has a disadvantage making code less readable, as it might
be hard to see what is called