I am mostly familiar with Clojure as opposed to CL (have read a bit of Practical Common Lisp) or Scheme (have read a decent chunk of SICP), but there are some pieces here and there.
Clojure is immutable by default, which is a pretty big difference AIUI. It also has a pretty strong emphasis on concurrency, including a bunch of primitives for such. It uses STM.
A bunch of data structures are first class, like vectors, maps, and sets. There are literals for each of them, and you can use maps and sets as functions. You can also use keywords as functions against maps to e.g. retrieve the :name value from a list of maps:
Sad (for me) difference: tail call optimization, done automatically in Schemes, unavailable in Clojure. I know of recur and trampoline, but recur does not (I think?) work with mutual recursion and if I wanted to use trampoline, I'd be coding in JavaScript :)
Personally I dislike Clojure's syntax - Scheme's feels more pure, cleaner. It's a bit like PERL vs. Python, a matter of taste largely.
I don't believe recur works with mutual recursion. I wish I understood mutual recursion better, frankly. Got any pointers about how one might use it in practice? In general my Lisp-fu could use a lot of improvement.
I would call it more like Ruby vs. Python — c'mon, perl? that's just mean — but yes, we're quibbling. :)
>Got any pointers about how one might use it in practice?
The meta-circular evaluator is a prime example of mutual recursion. It consists of two functions, eval and apply, which call each other in a circular fashion until a result is reached.
Again, I don't have much experience with Scheme; I've read about half of SICP. So that's a huge caveat here.
It may have been misleading to say that Clojure was immutable by default. It is immutable; it does not have set! or the moral equivalent. You can use concurrency primitives to update in place (sort of? AFAICT you're mutating the var itself, not the data it contains) but each primitive comes with its own semantics.
As far as first-class is concerned: in Clojure, maps, sets, and vectors are functions. They're functions of their data as well as being data, and there are literal representations for each.
Examples:
user=> ({:foo 1} :foo) ;; maps are functions
1
user=> (:foo {:foo 1}) ;; keywords are functions, too
1
user=> (filter :name [{:name "foo"} {:surname "baz"}]) ;; filter by key
({:name "foo"})
user=> ([\a \b \c] 2) ;; access vector by index
\c
user=> (#{1 2 3} 4) ;; test set membership
nil
user=> user=> (filter #{2 4 6} (range 1 10)) ;; filter by set membership
(2 4 6)
To what extent is any of that true of Scheme? I am reasonably certain that these data types can't operate as functions in Scheme. Glancing at such as the Racket docs, my impression is that generally you use a specific set of functions to interact with each type (e.g. hash-map-get, hash-has-key?, set-member?).
This is rather tricky subject - to understand real behavior - what is behind this or that a syntactic sugar.
In Scheme, for example, we could implement any data-structure as a closure, which accepts messages. There is no difficulty in writing such wrappers - it is just a closure which return another closure which accepts "messages", and following some protocol, returns, for example, another closures to be called for a certain action (generators, iterators, etc.)
We could teach the read function to recognize any kind of wired syntax we wish, and constructing appropriate data-structures with type-tags attached to them. But it will become a mess.
The real data-structures, however, its representation is very different thing. In Clojure, I suppose, it is based on built-in Java types and generic interfaces, such as Iterable or whatever it is. So, they are ordinary Java objects, without any magic in it.
In Scheme or CL it depends on the implementation, the choices made by developers. So, for example, Gambit-C and MIT Scheme are quite different in how they implement hash-tables or vectors. My guess is that, say Alegro CL and CMUCL are also very different, yet they all conform to some standards (CLtL2, ANSI).
Having very different implementations with different set of compromises is a strength.
So, in my opinion, there is absolutely nothing special in this syntactic constructions, moreover, it is not that difficult to implement them using closures and macroses. In arc.arc you could see how strings and tables were implemented.
Another issue is, should we add all this wired syntax to what we call Lisp? In my opinion doing this ruins Lisp and the result is some very different in a look-and-feel language. Calling it Lisp is, well, confusing, at least to me. Arc is a Lisp, no doubt. CL and Scheme are Lisps, for sure. Clojure is Java with lisp-like syntax, if you wish.)
The old rules says that there must be very heavy reasons to add any new symbol or a keyword into a language, and that the same things shall look the same, and different - differently. For me, personally, Arc is a proper approach, while Clojure is, well, a mess.)
>So, in my opinion, there is absolutely nothing special in this syntactic constructions, moreover, it is not that difficult to implement them using closures and macroses. In arc.arc you could see how strings and tables were implemented.
You're right, there is nothing special about what Clojure does; everything could be implemented in Scheme just fine. The real advantage comes from the fact that these things are there "out of the box" so that libraries are written to use them.
There is a lot of inertia when it comes to libraries in a language. In some sense, you only get one chance to get it "right" before everything becomes interdependent and you can no longer change anything. Clojure's advantage is simply that it presented a chance to start over and get things right from the beginning.
Right, yes: what ships with the language shapes the way the language is used, and has wider impacts beyond what you could theoretically or even practically do with it.
A dumb example would be a Option/Maybe type in Java. You could write a good approximation. But none of the standard libraries would use it, let alone any user supplied libs. Lisps are different in some very important ways, but since this is in part a meatspace phenomenon, it's still a challenge. Somebody went so far as to call it The Lisp Curse[0].
Anyway, syntax is a matter of taste, though I think people focus too much on it one way or the other— people don't like s-expressions b/c parens, people don't like Clojure b/c brackets/braces/whatever.
So while I agree that Clojure syntax isn't a huge game-changer, it can and does improve the quality of life for some people, myself included. Built-in literals are a big deal. And there is a lot to be said for being able to use these as primitives on day 1 of learning Clojure, esp. as your first Lisp.
In Racket, any custom data type can be turned into a function by using the `prop:procedure` structure type property. It's usually not done with things like maps and sets because this kind of "punning" is not idiomatic. Being a function is not actually a necessary condition for being "first-class" though.
I agree that it's not necessary. But it's a very strong signal, and thus illustrative.
AFAICT there is not a well-understood definition of first-class outside of "first-class functions." If I had to nail it down, I'd probably include the notion of first-class as applied to functions, plus:
* literal representations (where applicable)
* idiomatic, as demonstrated/enabled by the standard library
* well-integrated/interop with other primitives
This is just off the top of my head. And sometimes this is relative to other constructs in the language, or fuzzier, like Haskell lists vs. maps vs. sets.
> AFAICT there is not a well-understood definition of first-class outside of "first-class functions."
Why not just the same notion of "first-class" as functions? In other words, that the feature is actually represented by a run-time value that can be passed around freely and stored. This is the usual definition of "first-class" that I hear most people use in the programming languages world. Examples include first class control (continuations), first class references (boxes, mutable cons cells, etc.), first class environments, first class modules (see OCaml, units, etc.), first class labels, and so on.
Scheme has set-car!, vector-set!, hash-set! etc to modify its standard containers. In clojure those containers are immutable. However, you could create equivalent immutable containers in Scheme, perhaps that's why he said "by default".
Clojure is immutable by default, which is a pretty big difference AIUI. It also has a pretty strong emphasis on concurrency, including a bunch of primitives for such. It uses STM.
A bunch of data structures are first class, like vectors, maps, and sets. There are literals for each of them, and you can use maps and sets as functions. You can also use keywords as functions against maps to e.g. retrieve the :name value from a list of maps:
Lambda literals are nice: They can take multiple args (e.g. %1 %2) instead of just %.Threading macros are somewhat closer to Haskell's syntactically straightforward function composition:
I'm sure there's more. Maybe a more experienced Lisper can chime in.