
Challenging Clojure in Common Lisp (2014) - krat0sprakhar
https://chriskohlhepp.wordpress.com/functional-programming-section/metacircular-adventures-in-functional-abstraction-challenging-clojure-in-common-lisp/
======
zengid
I feel like the author didn't do this as a way to bash Clojure but to try and
progress Common Lisp. Anecdotally, I remember Hickey saying something about a
stagnation in the CL community, so if Clojure inspires cross-pollination of
ideas then that's a good thing.

For instance, Simon Peyton Jones describes [1] how they took the ideas around
STM from 'unsafe' languages and put it into Haskell, developed it further, and
then later the 'unsafe' languages took the Haskell innovations and put it back
into their languages.

Clojure isn't perfect, and Common Lisp isn't perfect, but hey, they're better
than programming in assembly. We should be stoked about someone trying to
innovate; why do these posts always turn into language wars?

[1] [https://youtu.be/iSmkqocn0oQ](https://youtu.be/iSmkqocn0oQ) around 3:50,
but the video is short so it's worth watching the whole thing.

~~~
brudgers
I didn't take it as Clojure bashing either. On the other hand, it does seem to
be a sort of back-footed defense of Common Lisp, to _me_. Partially because it
seems to miss acknowledgement that part of the goal of Clojure was to bring
the level of abstraction that Common Lisp affords to the JVM, for example
Common Lisp has the abstraction of Seq(uences) and functions that operate upon
them.

But the big elephant to me is that Common Lisp provides a very strong set of
Von Neumann abstractions in addition to its functional abstractions. Lists are
an abstraction that maps to sequential reads of a contiguous block of memory.
Setf has pointer and offset semantics. And the much maligned `loop` is a
brilliant abstraction over iteration that lets a program be explicit about the
_why_ of a loop.

That's not to say there are not advantages to functional programming. But the
popularity of Go and Python etc. suggest that abstractions that run counter to
functional programming mores is not a reason to find fault in Common Lisp.
Like Clojure, it's a big language that tries to meet programmers where they
are in the projects that they work on. Nobody faults Clojure for allowing
Java's mutable vectors because they're there in the language for practical
reasons.

------
_halgari
STM and concurrency...sure those are "aspects" of Clojure, Clojure's biggest
strength is that it's data-centric. The vast majority of the language is
focused around manipulating hashmaps, vectors, sequences, etc, and do that in
an efficient way. All the concurrency stuff is just icing.

So I really have to sit back and shake my head when the author says he's going
to be as good as Clojure, then goes off into the weeds with custom syntax, STM
and actors. Really? Why actors?

This would have been a much better article if it just left Clojure out of the
discussion since whenever the author talks about the language he's mostly
wrong.

And as always, lies, damn lies, and benchmarks:
[https://benchmarksgame.alioth.debian.org/u64q/compare.php?la...](https://benchmarksgame.alioth.debian.org/u64q/compare.php?lang=clojure&lang2=sbcl)
If you're going to use a phrase like "faster", at least spend some time to
define the word.

~~~
m0skit0
Isn't a functional language supposed to be function centric?

~~~
_halgari
Sure, but what do those functions do? In clojure they manipulate data. That's
pretty much the gist of the language: using pure functions to manipulate data.

Almost all the work I do these days as a software engineer is data
transformation. Going from a HTTP request, which is data (even the header is a
hashmap), and a HTTP body, which is data, into some business logic that
eventually writes to a database in a different format.

Even the most complex systems I've built containing dozens of servers and
multiple databases, queues, http servers, etc. All boil down to transforming
data from format A to format B perhaps with conditional logic applied.

So yes, Clojure is a functional language, but functions are just a tool to be
used to get the actual work done of transforming data.

------
raspasov
If the author reads this:

Can you elaborate more on what you mean by "lackluster performance"? What is
the use case? If you're looking for top speed in terms of C/assembly
performance - I'd say yes, probably the JVM will get in your way. However, I
spent months building a database/key-value store in Clojure and it's quite
doable to write very high performance code in Clojure as long as you put the
right type hints everywhere. Tools like YourKit can help you identify the
bottlenecks in your program. Again, depends on the use case, but Clojure makes
it very, very idiomatic to write code that makes full use of a multicore
system - something possible in the like of Java, C, et al but definitely not
idiomatic at all.

~~~
pron
(not the author)

> probably the JVM will get in your way

Not so much the JVM, but Clojure and how it uses the JVM (although, as you
say, it's possible to get not too far from top JVM performance with Clojure).
It's fairly easy to get C performance (and even beat it in concurrent code)
for the same amount of effort on the JVM. Currently, the main handicap the JVM
has is the lack of arrays-of-structs which may cause lots of cache-misses, and
requires less-than-elegant code to overcome. This, thankfully, is being
addressed by the addition of value types.

~~~
WildUtah
_It 's fairly easy to get C performance (and even beat it in concurrent code)
for the same amount of effort on the JVM._

Ha.

That's what we were promised back in the mid-1990s. It's no more true today
than it was then. Well written Java will always be about 2-4x slower than
similar C code.

JVM applications have many compensating advantages, including the
aforementioned ease of exploiting concurrency. That doesn't erase the reality
of single thread performance where Java has never, in twenty years of strong
expert effort, caught up.

~~~
fnordsensei
Agreed, in that the performance benefits you can get out of a higher level
language will often depend on the stuff where the limits of human cognition is
the performance boundary. As computing gets faster in general, the practical
considerations should shift more towards the performance you can get out of an
entire system than unit-for-unit performance. On that level, human
comprehension often seems to be what needs to be accounted for.

I think of it as problem akin to how you can make traffic run better in an
entire city rather than optimising the performance of an engine in a single
car. It doesn't invalidate making more performant engines, but it's a
different level of consideration.

When saying, "JVM is as performant as C", it's easy to run the numbers and see
whether it's true or not, objectively. The caveat that GP throws in is, _for
the same amount of effort_. Then you need to specify which human and under
what circumstances, and the entire thing gets hairier.

~~~
WildUtah
_As computing gets faster in general, the practical considerations should
shift more towards the performance you can get out of an entire system than
unit-for-unit performance._

Those days are over, unfortunately. Computing stopped getting faster in
general around 2010. And that also changes the calculus around single thread
performance. You can no longer count on hardware eventually to solve
performance problems.

~~~
lvh
Which is precisely why it matters that I can write functionally pure code that
is trivial to parallelize. At $FORMER_WORK, I even wrote (the same!) code as
reasonably idiomatic-looking Clojure (essentially, reducer fns) that
transparently runs locally single-threadedly, multi-threadedly, and on Hadoop.

That C program might be fast, but it's not a great tool for processing
petabytes of data on a stampeding herd of angry elephants. So, I think
performance arguments need a little nuance about what you're doing.

(My experiments include soft real time with deadline scheduling. Clojure's
fine.)

~~~
tensor
I can't remember the source, but I remember reading about a case where someone
replaced a large hadoop cluster with one node running highly tuned C.
Distributed computing comes with a lot of overhead and you might be surprised
at just how much you can get out of a highly tuned C application.

------
hellofunk
> Somehow I could not shake the feeling that homoiconic macros were intimately
> tied to the very essence of functional programming

That's the first time I've heard anything like this. Lots of great FP
languages exist that are not dynamic or offer such macros, why would they be a
requirement for FP?

~~~
fnordsensei
It's not, he just couldn't shake shake the feeling. Kind of how you can't
shake the feeling that there _should_ be left-over pizza in the fridge,
knowing very well that you already ate it all.

I think many of us probably tend to associate certain styles and features with
functional programming that really are orthogonal to whether the program is
functional or not.

As I've had it explained to me, if you write your program using functions
whose output only depends on the arguments provided to that function, and
nothing else, then you have yourself a functional program. But then again,
people tend to treat the concept like a scale, with some programs (or
languages) being more or less functional than others.

~~~
nickbauman
I think the problem hovers around the idea of _first class_ ness. In Clojure,
I can use a function like '<' as a key in a hashmap. In Python, I can't.
Because '<' isn't a function, it's an operator in Python. First classedness
has to do with non-complectedness. Python has operators and statements in
addition to expressions. Clojure, because it's a Lisp, gets by with just
expressions.

~~~
BeetleB
Actually, yes you can in Python - indirectly.

Most of the usual operators can be imported as functions:

    
    
        from operator import lt
    

will get you '<' as a function.

~~~
nickbauman
But I have to use lt as a key, I can't use <. Important difference, no?
Writing a DSL is much easier with that difference.

------
lvh
You mentioned a desire to get some visibility into the runtime effects of your
code. You get that (ASM dumps) out of CL, but you also get JVM bytecode and
associated ASM dumps out of Clojure, even if your code is doing FFI. I go into
detail on this in my Conj talk:
[https://www.youtube.com/watch?v=Lf-M1ZH6KME](https://www.youtube.com/watch?v=Lf-M1ZH6KME)

------
lvh
You mentioned that you wanted something non-hosted. Unless I missed it, the
arguments are roughly performance (not really made) and visibility (not true,
see [1]). Hosted versus non-hosted seems like a property without a particular
intrinsic value judgement. I'm going to argue that for a Lisp, being hosted is
a great idea.

Firstly, JS is a very credible and easy to use target for Clojure. It has
source map support and produces aggressively optimized JS via the Google
Closure compiler. (Parenscript, the CL competitor, can claim neither.) I've
had plenty of code where I literally rename the "clj" extension to "cljc" and
it magically runs in the browser.

Secondly, I get to use ample libraries in both cases because they already have
thriving ecosystems that are completely independent from all of these
parentheses. For example, today I'm writing AWS code, and I get to use AWS's
Java SDK painlessly. If I'm on CL, it appears I'm out of luck.

Being a hosted language, in and of itself, hence seems like a clear choice if
it isn't for the (not made) performance argument. So, perhaps we should talk
about that instead :)

[1]:
[https://news.ycombinator.com/item?id=13349747](https://news.ycombinator.com/item?id=13349747)

~~~
junke
See ABCL ([http://abcl.org/](http://abcl.org/)) and JSCL
([https://github.com/jscl-project/jscl](https://github.com/jscl-project/jscl))

~~~
lvh
Some Java:

    
    
      public class Main {
          public int addTwoNumbers(int a, int b) {
              return a + b;
          }
      }
    

Calling it from ABCL:

    
    
      (defun void-function (param)
        (let* ((class (jclass "Main"))
               (intclass (jclass "int"))
               (method (jmethod class "addTwoNumbers" intclass intclass))
               (result (jcall method param 2 4)))
          (format t "in void-function, result of calling addTwoNumbers(2, 4): ~a~%" result)))
    

Calling it from Clojure:

    
    
      (defn f []
        (prn "calling addTwoNumbers from f" (.addTwoNumbers (Main.) 2 4)))
    

Note that the Clojure version creates the object, and the ABCL version doesn't
do that for you; you still need to instantiate a Main yourself.

Also, my concrete claim was that I could use AWS easily. I can use both the
SDK directly quite painlessly (see above) or use
[https://github.com/mcohen01/amazonica](https://github.com/mcohen01/amazonica).
Where is the equivalent CL library?

~~~
junke
Use JSS
([http://abcl.org/svn/tags/1.3.0/contrib/jss/README.markdown](http://abcl.org/svn/tags/1.3.0/contrib/jss/README.markdown)):

    
    
        ;; During initialization
        (require 'abcl-contrib)
        (require 'jss)
    

Then, for example:

    
    
        CL-USER(1): (in-package :jss)
        JSS(2): (lambda (x y)
                  (format t
                          "~&addTwoNumbers(~A, ~A): ~A~%" x y
                          (#"addTwoNumbers" (new 'Main) x y)))
        #<FUNCTION (LAMBDA (X Y)) {7EEE31B2}>
    
        JSS(3): (funcall * 20 30)
        addTwoNumbers(20, 30): 50
        NIL
    

The same goes for any Java library. As for AWS, I have no experience with it.
Looking around, there seems to be [https://github.com/hargettp/hh-
aws](https://github.com/hargettp/hh-aws). Btw, I am not attacking Clojure, but
the idea that Clojure is the only useful Lisp available on the JVM.

~~~
lvh
That is a lot better, thanks. hh-aws is unmaintained and only supports a tiny,
tiny fraction of AWS.

------
doall
To understand concurrent programming in Clojure, at least you should read [1].
In general, the symbolic STM (Refs) is not often used in Clojure programming.
Atoms (a wrapper of the Java AtomicReference) is much more lightweight and is
used in most cases.

[1]: [http://clojure-
doc.org/articles/language/concurrency_and_par...](http://clojure-
doc.org/articles/language/concurrency_and_parallelism.html)

------
macmac
> "Object orientation falls short here in that it does readily permit
> mathematical composition."

Surely "does" is meant to be negated?

------
_halgari
Can we get some sort of edit to this title to show that it's from back in
2014? A ton of work has gone into Clojure since then and in some aspects the
language has gotten orders of magnitude faster (not that it was really that
slow to begin with).

~~~
sctb
Thank you, we've updated the title.

------
minikomi
The end result seems a little strange to me. Why doesn't it KNEAD and BAKE
without being told, and yet DECORATE's just by adding sugar?

Also, this is a perfect task for core.async / core.match in clojure.
Definitely not 6 days worth of work.

