No, this isn't just about Clojure. You could do similar things in Scheme, or CL, or even Python or Ruby.
What's cool about this isn't that we've managed to put functions in a data structure. It's that doing this in a particular way allows us to describe computations in a declarative way. This declarative specification opens up lots of interesting avenues to do new things with our code that weren't available before.
Of course the idea of declarative programming isn't new either, but we think this particular instantiation is cool because it's extremely simple and close to the language. Writing a Graph doesn't feel any heavier than writing the corresponding non-declarative code, and this is crucial for making it actually useful in many kinds of situations (rather than just cases where heavy lifting is necessary, like distributed stream processing for example).
Yes, using code as data and creation of the code when a program is running is the most powerful feature of Lisps.
My point is that Clojure isn't a Lisp and JVM isn't the best possible platform, and embedded in a Lisp DSLs are even more powerful because of the common (for code and data) underlying data structure - conses.
Of course, I know the counter-points about "Java is everywhere" and "Interloop with existing Java code".
As of heavy lifting or whatever to call it, decent CL implementation would be faster (compiled native code), consume much less resources (predictable memory allocation patterns), and more manageable (behavior much less depended of the system load and how other processes behave).
I programmed in CL for several years exclusively, and think it's an awesome language. But I also really love Clojure, and think it's the the most beautiful Lisp (or S-expression-oriented language with a read-eval-print loop, if you prefer) I've had the opportunity to explore. To each their own.
Thank you for an alternative definition. In my opinion adding more data-structures into a Lisp ruins it. It is a List Processing, for John's sake.)
More seriously, having exactly one common data-structure for code and data is what holds everything together, the source of power, compactness, elegance and readability.
A small additional effort, a self-discipline of using lists correctly (remembering the costs) everywhere and using hash-tables and arrays only when absolutely necessary is the way to write decent Lisp code.
Lisp is an unique combination of design decisions which together produces an unique programming language.
It seems like this definition is general enough to be applied to almost any language, but it only seems so.
The key idea here is that it must be a minimal set of features, that is good-enough. The more bloated a language is - the less good it is.
So, what is the minimum set of features for a Lisp?
- common underlying representation for code and data (a list structure based on conses).
- one common syntax for describing code and data (prefix notation and paranthesys)
- one general evaluation rule for all possible expressions with an exeption of *very few* special cases (less special forms - better).
- general, environment-based evaluation strategy based on lexical scooping (the way to create closures).
- tag-based type system - data has a type, not a variable (everything-is-a-pointer semantics).
- list-aware reader function which perform transformations of lists
- pointer-based, zero-copying FFI
- REPL
Together this is what a modern Lisp is. This set of features produces the power and elegance of a Lisp. It holds for Schemes, CLs and Arc.
Clojure went its own way to stuff anything into a language (and ruin the miracle of a Lisp) and instead of being based on so to speak, real-memory data-structures uses JVM's abstractions, which together renders something that looks like a Lisp, but is completely different language, some next level, but worse, farther away.
It also worth of another analogy. Modern Lisp is already a creole language - a language evolved by generations of speakers, while Clojure (and most of other languages) is still a pidgin.)
Let's be clear though - this is your personal list of requirements. Be that as it may you should address how each of your points is in conflict with clojure.
Oh, of course, cluttering the code with explicit conversion from one data-structure into another is a much better way, much more lines of code, bigger self-esteem, better salary. Java world.
There is an example (very clever, no doubts)
(defn keywordize-map
"Recursively convert maps in m (including itself)
to have keyword keys instead of string"
[x]
(condp instance? x
clojure.lang.IPersistentMap
(for-map [[k v] x]
(if (string? k) (keyword k) k) (keywordize-map v))
clojure.lang.IPersistentList
(map keywordize-map x)
clojure.lang.IPersistentVector
(into [] (map keywordize-map x))
x))
btw, this code is really clever, while in typical clojure project there are tons of meaningless conversions.
So which True Lisp do you prefer that lacks analogues to the types used above (String, Keyword, List, Map, Vector) in which such a function wouldn't be applicable?
I know it's not a Common Lisp implementation. Most modern Schemes have equivalents. The only difference between Clojure and most Lisps in this case is that its collections are immutable. So how has Clojure sinned in this regard?