[Herein I mostly mean Common Lisp when I talk about Lisp. I have no experience with large projects in Clojure or Scheme.]
Most modern software -- Lisp programs included -- is written by multiple programmers in both coordinated and uncoordinated teams. Lisp can easily adapt to team programming but SWE needs to be augmented to handle programming projects that operate at a meta-level, which is natural with Lisp. If you write code at a meta level with ordinary SWE it can lead to disaster.
SWE needs tooling, procedures, and policies to deal with macros, package naming, bootstrapping, multiple-inheritance classes and first-class methods, etc. before it can really support Lisp. Lisp projects that have added such SWE features manually tend to succeed; those that don't tend to have difficulty with Lisp. My point is the team has to do the SWE work; SWE tools operating in default mode tend not to be good enough for Lisp.
Funky, McCarthy had a whole team working on and with Lisp.
After many years working in that environment...
and right now most of the world is slowly migrating to a lisp/ml word without knowing...
It's just time.
MLs took ideas in LISP much further and imposed powerful type systems on them, so they appeal to a smaller audience than LISP does.
And the multi-paradigm languages simplified ideas from LISP to be usable for a broader audience, and brought in new ideas from elsewhere.
So I don't think SE has failed to reach LISP, if anything, it's surpassed it.
In Common Lisp, using lists for most data structures is a mark of a poor programmer. If you think Lisp is about list processing, you perhaps need to become familiar with how Lisp has evolved since the 1970s.
``` (setq a (make-hash-table)) (setf (gethash 'color a) 'brown) (setf (gethash 'name a) 'fred) (gethash 'color a) => brown (gethash 'name a) => fred (gethash 'pointy a) => nil ```
vs
``` a = { color:'brown', name:'fred' } ```
(defparameter a '((color brown)
(name fred)))
(defun make-hashmap (pairs)
(let ((ht (make-hash-table)))
(mapc (lambda (pair)
(setf (gethash (car pair) ht) (cadr pair)))
pairs)
ht))
(defun showhash (hash-table)
"Show hash-table"
(maphash #'(lambda (key value)
(format t "~%~S : ~S" key value))
hash-table))
(showhash (make-hashmap a))(Yes, you can carefully bind
*readtable*
before compiling code, but methods for doing so are ad hoc and not standardized. This is a perfect example of a convention that Common Lisp programmers need to establish formally when using CL for software engineering.)You declare a readtable like this: https://github.com/fiddlerwoaroof/objc-lisp-bridge/blob/mast...
And use it like this: https://github.com/fiddlerwoaroof/objc-lisp-bridge/blob/mast...
{:color “brown” :name “fred”} CL-USER> (set-macro-character #\{
(lambda (str char)
(declare (ignore char))
(let ((*readtable* (copy-readtable *readtable* nil))
(keep-going t))
(set-macro-character #\} (lambda (stream char)
(declare (ignore char) (ignore stream))
(setf keep-going nil)))
(let ((pairs (loop for key = (read str nil nil t)
while keep-going
for value = (read str nil nil t)
collect (list key value)))
(retn (gensym)))
`(let ((,retn (make-hash-table :test #'equal)))
,@(mapcar
(lambda (pair)
`(setf (gethash ,(car pair) ,retn) ,(cadr pair)))
pairs)
,retn)))))
T
CL-USER> {:foo 100500 :bar 42}
#<HASH-TABLE :TEST EQUAL :COUNT 2 {1005879463}>
CL-USER> (alexandria:hash-table-alist #v1)
((:BAR . 42) (:FOO . 100500))
CL-USER>
And define a printer for the hash to output it as desired: CL-USER> (defmethod print-object ((obj hash-table) stream)
(princ "{" stream)
(loop with indent = nil
for key being the hash-keys in obj using (hash-value value)
when indent
do (format stream "~% ")
unless indent
do (setf indent t)
do (format stream "~S ~S" key value))
(princ "}" stream))
#<STANDARD-METHOD COMMON-LISP:PRINT-OBJECT (HASH-TABLE T) {10040C9F83}>
CL-USER> {:foo 100500 :bar 42}
{:FOO 100500
:BAR 42} (defparameter a '((color brown)
(name fred)))But you could do:
(def foo {:color "brown" :name "fred"})
Anytime you want to get the color out you can just call the :color function and pass the map (:color foo)
=> "brown"
(:name foo)
=> "fred"
Likewise, we can call the map as the function and pass the key as the argument (foo :name)
=> "fred"
We also have nice ways to destructure and bind values in maps when passed as parameters to functions (defn my-func [{:keys [color name]}]
(println "Color: " color)
(println "Name: " name))
(my-func foo)
=>
Color: brown
Name: fred>vs
>a = { color:'brown', name:'fred' }
For such simple data you don't need to use a hashtable in Lisp. You just do:
(:color "brown" :name "fred")
then you get the name using:
(getf x :name)
Btw, anything you can represent in JSON, you can also represent in Lisp s-expressions. But not the other way around(!)
Hashtables, in Common Lisp, are intended for big amounts of data, that's why CL includes several options to tune their performance.
This works, but it's a plist and thus O[n] for lookup. I assumed the questioner wanted an O[1] data structure; thus a hash table is the better answer. (Granted it makes little difference when n is small.)
There was a test on some Common Lisp implementations, of hashtables versus plists on access speeds.
On small (<30?) datasets, plists were much faster than hashtables. Bigger than that, the constant access speed of hashtables makes HTs a more sane choice.
The other thing is memory consumption -- on CL, plists are very frugal.
Recall that the original claim was that software engineering hasn't caught up to LISP. Making metaprogramming trivial by using an identical presentation for code and data is the key insight of LISP, and we do a lot of metaprogramming in many languages, so yes, we've caught up with LISP.