Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 2 years ago.
Improve this question
A claim that I recall being repeated in the Clojure for Lisp Programmers videos is that a great weakness of the earlier Lisps, particularly Common Lisp, is that too much is married to the list structure of Lisps, particularly cons cells. You can find one occurrence of this claim just at the 25 minute mark in the linked video, but I'm sure that I can remember hearing it elsewhere in the series. Importantly, at that same point in the video, we see this slide, showing us that Clojure has many other data structures than just the old school Lispy lists:
This troubles me. My knowledge of Lisp is quite limited, but I've always been told that a key element of its legendary metaprogrmmability is that everything - yes, everything - is a list and that this is what prevents the sort of errors that we get when trying to metaprogram other languages. Does this suggest that by adding new first-class data structures, Clojure has reduces its homoiconicity, thus making it more difficult to metaprogram than other Lisps, such as Common Lisp?
is that everything - yes, everything - is a list
that has never been true for Lisp
CL-USER 1 > (defun what-is-it? (thing)
(format t "~%~s is of type ~a.~%" thing (type-of thing))
(format t "It is ~:[not ~;~]a list.~%" (listp thing))
(values))
WHAT-IS-IT?
CL-USER 2 > (what-is-it? "hello world")
"hello world" is of type SIMPLE-TEXT-STRING.
It is not a list.
CL-USER 3 > (what-is-it? #2a((0 1) (2 3)))
#2A((0 1) (2 3)) is of type (SIMPLE-ARRAY T (2 2)).
It is not a list.
CL-USER 4 > (defstruct foo bar baz)
FOO
CL-USER 5 > (what-is-it? #S(foo :bar oops :baz zoom))
#S(FOO :BAR OOPS :BAZ ZOOM) is of type FOO.
It is not a list.
CL-USER 6 > (what-is-it? 23749287349723/840283423)
23749287349723/840283423 is of type RATIO.
It is not a list.
and because Lisp is a programmable programming language, we can add external representations for non-list data types:
Add a primitive notation for a FRAME class.
CL-USER 10 > (defclass frame () (slots))
#<STANDARD-CLASS FRAME 4210359BEB>
The printer:
CL-USER 11 > (defmethod print-object ((o frame) stream)
(format stream "[~{~A~^ ~}]"
(when (and (slot-boundp o 'slots)
(slot-value o 'slots))
(slot-value o 'slots))))
#<STANDARD-METHOD PRINT-OBJECT NIL (FRAME T) 40200011C3>
The reader:
CL-USER 12 > (set-macro-character
#\[
(lambda (stream char)
(let ((slots (read-delimited-list #\] stream))
(o (make-instance 'frame)))
(when slots
(setf (slot-value o 'slots) slots))
o)))
T
CL-USER 13 > (set-syntax-from-char #\] #\))
T
Now we can read/print these objects:
CL-USER 14 > [a b]
[A B]
CL-USER 15 > (what-is-it? [a b])
[A B] is of type FRAME.
It is not a list.
Let's break the word "homoiconicity" apart.
homo-, meaning "same"
iconic, here meaning something like "represented by"
-ity, meaning "something having such a property"
The quality that makes macros possible (or at least much easier) is that the language itself is represented with the same data structures that you use to represent other objects. Note that the word is not "monolistism", or "everything is a list". Since Clojure uses the same data structures to describe its syntax as it does to describe other values at runtime, there's no excuse to claim it is not homoiconic.
Funnily enough, I might say that one of the ways that Clojure is least homoiconic is a feature that it actually shares with older lisps: it uses symbols to represent source code. In other lisps, this is very natural, because symbols are used for lots of other things. Clojure has keywords, though, which are used much more often to give names to data at runtime. Symbols are quite rarely used; I would go so far as to say that their primary usage is for representing source code elements! But all of the other features Clojure uses to represent source code elements are frequently used to represent other things as well: sequences, vectors, maps, strings, numbers, keywords, occasionally sets, and probably some other niche stuff I'm not thinking of now.
In Clojure, the only one of those extra data structures that is required for some language constructs, besides lists, are vectors, and those are in well-known places such as around sequences of arguments to a function, or in symbol/expression pairs of a let. All of them can be used for data literals, but data literals are less often something you want to involve when writing a Clojure macro, as compared to function calls and macro invocations, which are always in lists.
I am not aware of anything in Clojure that makes writing macros more difficult than in Common Lisp, and there are a few features there distinct to Clojure that can make it a little bit easier, such as the behavior where Clojure backquoted expressions by default will namespace-qualify symbols, which is often what you want to prevent 'capturing' a name accidentally.
As previous answerers pointed out, "homoiconicity" means nothing else than that the famous "code is data": That the code of a programming language consists 100% of data structures of that language, thus is easily build-able and manipulateable with the functions in the language usually used to manipulate the data structures.
Since Clojure data structures include lists, vectors, maps ... (those you listed) and since Clojure code is made of lists, vectors, maps ... Clojure code is homoiconic.
The same is true for Common Lisp and other lisps.
Common Lisp contains also lists, vectors, hash-tables etc. .
However, the code of Common Lisp sticks more rigidly to lists than Clojure does (function arguments are in a argument list and not in a vector like in Clojure).
So you can more consistently use functions to manipulate lists for metaprogramming purposes (macros).
Whether this is "more" homoiconic than Clojure is a philosophical question.
But definitely you need a smaller set of functions for manipulation of the code than in Clojure.
Related
Context
Our team is using some more functional patterns in other languages (IE javascript and ruby) and we've had some recreational conversations on standardized naming. My familiarity is largely limited to Clojure and Ramda.js.
Both Clojure and Ramda have assoc and Clojure has assoc-in with a Ramda version at assocPath. I'm guessing Ramda cribbed its name from Clojure here.
I note that common lisp doesn't seem to even have a clear comparable to assoc-in. See this conversation.
The Question
What other lisps or lisp-likes have assoc and assoc-in comparables, and what do they call them?
Bonus Points
Do there exist any resources for "rosetta stone" inter-lisp comparison of function names? A place to easily see where comparable behavior has similar or different names, or to see where comparable names have similar or different behavior?
You're right that Common Lisp doesn't have assoc-in or something similar. I would say, overall, it has pretty basic support for hash-table operations in the standard library (which I attribute to lack of practical experience with them at the time of the development of the standard). However, even if more hash-table-related stuff was present, I still doubt something like assoc-in would be added. That's because, in Lisp, the preferred approach to deal with assignment is via the setf'able places machinery that involves the macro define-setf-expander.
For instance, the rutils library I am maintaining provides a lot of additional hash-table-related operators, as well as Clojure-like literal syntax for them. Yet, this is how I'd handle the assoc-in case using its primitives (including the ? operator that is a shorthand for generic-elt):
CL-USER> (ql:quickload :rutils)
CL-USER> (in-package :rtl-user)
RTL-USER> (named-readtables:in-readtable rutils-readtable)
RTL-USER> (toggle-print-hash-table)
RTL-USER> (let ((ht #{:a 1 :b 3}))
(setf (? ht :c) #{:d 33})
ht)
#{
:A 1
:B 3
:C #{
:D 33
}
}
I'd say that it's a question of taste which approach to prefer. To me, the Lisp one has more advantages: it's more uniform and explicit.
The other advantage is that the get-in part becomes really straightforward:
RTL-USER> (? * :c :d)
33
(Here, I use * to refer to the results of the previous REPL computation that happen to be the hash-table we created in the assoc-in exercise).
P.S. As for the Rosetta stones, here are a few attempts:
https://hyperpolyglot.org/lisp
https://lispunion.org/rosetta/
How in Clojure process collections like in Java streams - one by one thru all the functions instead of evaluating all the elements in all the stack frame. Also I would describe it as Unix pipes (next program pulls chunk by chunk from previous one).
As far as I understand your question, you may want to look into two things.
First, understand the sequence abstraction. This is a way of looking at collections which consumes them one by one and lazily. It is an important Clojure idiom and you'll meet well known functions like map, filter, reduce, and many more. Also the macro ->>, which was already mentioned in a comment, will be important.
After that, when you want to dig deeper, you probably want to look into transducers and reducers. In a grossly oversimplifying summary, they allow you combine several lazy functions into one function and then process a collection with less laziness, less memory consumption, more performance, and possibly on several threads. I consider these to be advanced topics, though. Maybe the sequences are already what you were looking for.
Here is a simple example from ClojureDocs.org
;; Use of `->` (the "thread-first" macro) can help make code
;; more readable by removing nesting. It can be especially
;; useful when using host methods:
;; Arguably a bit cumbersome to read:
user=> (first (.split (.replace (.toUpperCase "a b c d") "A" "X") " "))
"X"
;; Perhaps easier to read:
user=> (-> "a b c d"
.toUpperCase
(.replace "A" "X")
(.split " ")
first)
"X"
As always, don't forget the Clojure CheatSheet or Clojure for the Brave and True.
In Common Lisp, the let uses a list for a bindings, i.e:
(let ((var1 1)
(var2 2))
...)
While Clojure uses a vector instead:
(let [a 1
b 2]
...)
Is there any specific reason, other than readability, for Clojure to use a vector?
You can find Rich Hickey's argument at Simple Made Easy - slide 14, about 26 minutes in:
Rich's line on this was as follows
"Since we were talking about syntax, let’s look at
classic Lisp. It seems to be the simplest of syntax, everything is a
parenthesized list of symbols, numbers, and a few other things. What
could be simpler? But in reality, it is not the simplest, since to
achieve that uniformity, there has to be substantial overloading of
the meaning of lists. They might be function calls, grouping
constructs, or data literals, etc. And determining which requires
using context, increasing the cognitive load when scanning code to
assess its meaning. Clojure adds a couple more composite data literals
to lists, and uses them for syntax. In doing so, it means that lists
are almost always call-like things, and vectors are used for grouping,
and maps have their own literals. Moving from one data structure to
three reduces the cognitive load substantially."
One of the things he believes was overloaded in the standard syntax was access time. So vector syntax in arguments is related to the constant access time when you used them. He said:
Seems odd though as it only is valid for that one form...as soon as it is stored in a variable or passed in any way the information is 'lost'. For example...
(defn test [a]
(nth a 0)) ;;<- what is the access time of and element of a?
I personally prefer harsh syntax changes like brackets to be reserved for when the programmer has to switch mental models e.g. for embedded languages.
;; Example showing a possible syntax for an embedded prolog.
{size [],0}
{size([H|T],N) :- size(T,N1), N is N1+1}
(size '(1 2 3 4) 'n) ;; here we are back to lisp code
Such a concept is syntactically constant. You don't 'pass around' structure at runtime. Before runtime (read/macro/compile time) is another matter though so where possible it is often better to keep things as lists.
[edit]
The original source seems to have gone, but here is another record of the interview: https://gist.github.com/rduplain/c474a80d173e6ae78980b91bc92f43d1#file-code-quarterly-rich-hickey-2011-md
I want to judge if two values are of same type, but I found that the type of an empty list is clojure.lang.PersistentList$EmptyList rather than clojure.lang.PersistentList.
user=> (def la '())
#'user/la
user=> (def lb '(1 2))
#'user/lb
user=> (def t (map type [la lb]))
#'user/t
user=> t
(clojure.lang.PersistentList$EmptyList clojure.lang.PersistentList)
user=> (apply = t)
false
user=>
So, I'm wondering why is the type of an empty list different from that of non-empty lists and what's the correct way to tell if two things are of same type?
Don't rely on the concrete types of Clojure data structures. They are undocumented implementation details, and you have no guarantee that they won't change in future versions of Clojure.
It is much safer to rely on the abstractions (e.g. as defined by the IPersistentList or ISeq interfaces). These are much less likely to change in ways that might break your code (my understanding is that Rich Hickey is very big on backwards compatibility when it comes to abstractions. If you depend on a concrete implementation, I believe he would say it's your own fault if things break)
But even better, you should use functions in clojure.core such as seq? or list?, depending on exactly what it is you want to detect. Not only are these likely to maintain backwards compatibility for a long time, they also have a chance of working correctly on non-JVM versions of Clojure (e.g. ClojureScript).
Are there any differences between what in Common Lisp you'd call an atom, and a symbol?
Do these differences extend to other languages in the Lisp family?
(I'm aware that atom has a different meaning in Clojure, but I'm interested in the boundaries of what is a symbol.)
In Common Lisp, atom is precisely defined as any object that is not a cons. See http://l1sp.org/cl/atom for more details.
I don't know about other languages in the Lisp family.
'atom' is usually seen from list processing. In Common Lisp something is either a non-empty list or an atom. In former times an atom was also called 'atomic symbol', which is something slightly different. Now in Common Lisp atoms are not only symbols, but everything else which is not a cons cell (examples: strings, numbers, hashtables, streams, ...).
If something is not an atom (is a cons), the operations CAR, CDR, FIRST and REST can be used.
So atom is a group of data structure. A symbol is a certain data structure, which also happens to be an atom.
In Scheme, an atom is anything that is not a pair:
> (pair? 1)
#f
> (pair? '(1 2 3))
#t
> (pair? 'a)
#f
Thus symbols are atoms, just as numbers and strings. atom has a similar definition in Common Lisp, where the function (atom object) is defined to be (not (consp object)).
In Common Lisp, a symbol is very much like a variable in other languages, although more heavyweight (it isn't just a blank piece of memory big enough to hold a value). It is usually interned so it can be referenced by name, although it's possible to have anonymous symbols (much like memory in C that you might refer to only by pointer).
An atom is some value that isn't a cons cell. A symbol is an atom, and so is a number, a string, and lots of other things. The most common use of cons cells is in making up lists, although it's possible to use them in other ways.