An early Clojure implementation in Common Lisp
github.com
github.com
http://dotlisp.sourceforge.net/dotlisp.htm
http://groups.google.com/group/comp.lang.lisp/msg/e31b38c8cc...
What's the backstory on this, fogus? I've not read about a CL Clojure pre-dating the JVM implementation. Was this just a prototype or was there a plan to platform on CL at some point?
(defun set-hash-values (hash pairs)
(when pairs
(setf (gethash (car pairs) hash) (cadr pairs))
(set-hash-values hash (cddr pairs))))
(defun in-place-hash (stream char)
(declare (ignore char))
`(let ((hash (make-hash-table)))
(set-hash-values hash ',(read-delimited-list #\} stream t))
hash))
(set-macro-character #\{ #'in-place-hash)
(set-syntax-from-char #\} #\))
But to be honest, I think this {} stuff is an abuse of the hash structure. People are really looking to make quick anonymous classes/structs; you see this all the time in the perl world, and now we get to see this abuse promoted and propagated in Clojure. In that case, {} should really translate to a MOP-generated anonymous class.Also, you are stuck with the decision some developer made for your hash's efficiency trade-offs. In my reader macro, I left things at make-hash-table defaults, but that might not be appropriate for your situation. Do you want to be able to read quickly from your hash in exchange for slow writes, or do you want to be able to write quickly to the hash in exchange for slow reads?
On another note, the [] vector reader macro is much easier to create. I just wanted to prove to myself that I could write {}. But this additional syntax is annoying. In practice when programming Lisp, it is easy to slap in a bunch of ))))) until the right alternate parentheses highlights. Now I have to remember to slap in ] and } at the right spots, leaving me with )]}]]}})) noise. Maybe that is why CL's in-place vector syntax is #().
Anyway, I would hesitate to call {} and [] an innovation.
P.S. Do you really finish up all your parans yourself???
http://groups.google.com/group/comp.lang.lisp/browse_thread/...?
This discussion came up recently over the usage of Christophe Rhodes' generic sequence protocol extension to CL. Pascal Costanza makes a good point about the appropriateness of having an overly abstract interface tower, and I agree with him.
I have heard this point when comparing to Scheme (Racket). I did not understand how it applied to CL. I never found an issue dealing with the different datatypes (other than initially learning what API worked with what and what was the proper generic stuff to use).
> P.S. Do you really finish up all your parans yourself???
Yes. I found paredit and its derivatives really annoying, particularly when dealing with strings and other entities. I am not always closing entire functions, so the autoclose feature was also useless to me. I can see how it can both help and get in the way when dealing with [] and {}. I do not have a strong objection to that noise. See below for my real objection to the {} syntax; it is less about the {} and more about the reason for using it.
What wrong with that? If you have the need for an anonymous map, then you use a map. If you have the need for a named map then you use a record. Clojure gives you both options.
"Also, you are stuck with the decision some developer made for your hash's efficiency trade-offs."
In that case you just use a different type of hash map. In Clojure you have the choice of the literal syntax (which by the way manages efficiency concerns on your behalf), the hash-map, array-map, or sorted-map functions, or records, or Java maps for interop, or you can implement your own if none of those options work.
If you mean, "What is wrong with anonymous classes?", I have no answer. I do not mind the idea, so long as I can maintain the integrity of my data's interfaces.
If you mean, "What is wrong with implementing anonymous classes as hash maps?", then I point you towards issues of efficiency in memory and speed. Hash maps are intended to make key look-up quick for a variable set of not 100% known keys. If all you want is known key-value pairs, then you have some options that do the trick much more efficiently (examples --):
- structs (key is compiled out to be a quick pointer addition)
- classes (on top of the struct, you now have some abstraction capabilities that will help out future maintenance issues)
If you do not quite know what you want in the structure, but you have a small number of keys, an associative array with a decent accessor API is all you need. The few key comparisons will generally be faster than running them through a typical hashing algorithm.
When you provide a construct like this:
{ :foo 1 :bar 2 }
you are implying that you have a known set of keys and values. With a hash map, I would expect that someone would later add more key-value pairs, but in practice, code that starts with this initialization pattern treats it like a class rather than a hash, just modifying the values of the :foo and :bar attributes.I have also seen instances where developers would -- because it was easy to do with these hash maps -- slap in another key-value pair in some subfunction and then suffer interface issues down the road when they assume its presence in the wrong places. Something a little stricter would have at least given them a warning.
So just make it a struct -- anonymous if you need -- at minimum. That is the most generic, all-purpose form that could benefit from this kind of {} syntax.
> which by the way manages efficiency concerns on your behalf
Can you elaborate on this? If this means that there is a magical conversion to a stricter struct underneath the hood, then I have no place to complain about {} in Clojure's case. I have not encountered this notion before (other than as faith).
Clojure is built on abstractions. Most operations in clojure source code convert to calls to java interfaces. For example, a simple function call (map foo bar) compiles to a call to .invoke() method on map, a java object that implements clojure.lang.IFn. Similarly, all of clojure's datastructure operations boil down to calls to objects implementing the java interfaces clojure.lang.IPersistentCollection, c.l.IPersistentMap, c.l.IPersistentSet, etc.
To add a new datastructure, simply create a new class that implements the appropriate interfaces, and you'll get literal syntax support.
To answer your question, Clojure records are named classes that implement IPersistentMap so they're a drop-in replacement for "normal" maps, that also implement an additional interface so the compiler can generate java field lookups to access items without the cost of a hash lookup.
For example
(defn foo [map]
(println (:a map)))
(defrecord MyObj [a])
(foo {:a 1})
(foo (MyObj. 1))
Both calls work exactly the same, except the MyObj doesn't have hash lookups for the fields defined in the class.Awesome! Thank you for that. I saw people referring to it as a hash when you point out it is not, so I was confused. I sit corrected as well as happier with the Clojure design.
> Can you elaborate on this?
Sure thing. As a start the form `{:a 1, :b 2}` would start as an array-map because it's really fast to populate while the access efficiency is reasonable. At some point during the use of that map it will cross some critical threshold and become a hash-map providing (effectively) constant time lookup while still maintaining persistence. Additionally, behind the scenes Clojure uses mutable versions of the map for efficiency while again maintaining persistence.
Clojure provides different types of maps for use in different scenarios, but manages implementation efficiencies for you.
What made it an "innovation" was the decision to close off access to reader macros from the general public. It sounds like a nice idea to allow people to define the behavior of {}, [], etc. however they like. But the effect is splintering the language even further with fewer reusable idioms.
The Clojure idiom of always using [] for any place you have let-like destructuring makes it easier to quickly visually parse code, for example. The abbreviation for anonymous functions wouldn't be part of the core language if the corresponding reader macro was not claimed by Benevolent Dictator Hickey.
This decision may or may appeal to you, personally, but I think it is one of the things that has quickly allowed Clojure to quickly gain popularity relative to other Lisps.
http://www.reddit.com/r/programming/comments/7sfro/response_...
I would also hesitate to name any particular language feature of Clojure as having an impact on its popularity. I think it's due more to the current zeitgeist of "Lisp is cool, but I program in something else" and "concurrency concurrency concurrency."
The reason I think the "it's Lisp but not really a Lisp" is a major factor is the example offered by newLISP. Its biggest thing seems to be "all this macro and lisp-2 and lexical scoping stuff is nonsense, this is not scheme or common lisp, this is a simple language," and despite this paradoxical assertion it has developed a somewhat large user community (a lot larger than Arc's, for example).
The JVM is much less of a factor than many people suggest. Another good example in support of this and the "concurrency" thesis is node.js - it's not the first JS web server (by far), and it's not the first NIO-based web server, but it does combine the two at the right time.
PersistentHashMaps are not slow. If you think they are, you can provide a custom defrecord with the same fields to the functions of the API and get the all perf benefits without breakage.
> leaving me with )]}]]}})) noise
Guess you never heard of paredit.
> Anyway, I would hesitate to call {} and [] an innovation.
Whatever floats your boat man.
Here's hoping he gets around to writing it one day. :-)
Then he took time off work and wrote the Java bootstrap compiler.