My mental model of setf was wrong
simondobson.org
simondobson.org
Lisp has traditionally a bunch of different datastructures. Like cons cells with CAR and CDR accessors. RPLACA and RPLACD were the setters, from a time when characters were expensive, so that identifiers were short and abbreviated (RPLACA would be short for Replace CAR). Then there were property lists with accessors GET (-> Maclisp) or GETPROP (-> Interlisp). The setter was PUTPROP.
So for any datastructure we need to learn both a getter and the corresponding setter.
With SETF in Common Lisp we only need to know the getter. SETF knows for each getter a corresponding setter.
So for a datastructure FOO we don't need to know both GETBAR and BARSET!.
The getter is (GETBAR SOMEFOO) and the setter is (SETF (GETBAR SOMEFOO) SOMETHINGNEW). SETF then is a macro which knows how to set the thing.
Advantage: we don't have to learn ancient functions like RPLACA with calls like (RPLACA CELL 42), but we can write (SETF (CAR CELL) 42). Take the accessor and wrap a SETF around it, include the form for the new value.
In reality there is a bit more magic involved.
If you look at the history of languages like JS or B/C/etc. you better strap in for a crazy ride.
E.g., delete all middle conses of an assoc list, gluing the first and last one together:
[1]> (reduce #'rplacd (list (cons 'a 1)))
(A . 1)
[2]> (reduce #'rplacd (list (cons 'a 1) (cons 'b 2)))
(A B . 2)
[3]> (reduce #'rplacd (list (cons 'a 1) '(cons 'b 2) (cons 'c 3) (cons 'd 4)))
(A D . 4)
If someone finds an application for this, drop me a PM. :)New people learning Common Lisp don't need to know about rplacd, or not right away. Firstly, destructive manipulation of lists should be deemphasized; it is an advanced topic in itself which can produce bugs.
People who advance to system level work in Lisp, like doing work on implementations, will have to know it. rplacd can appear in code that cannot use setf, because it is processed before setf has been boostrapped.
You will run across rplacd in other people's code even if you don't use it yourself; so if you go beyond just writing programs for yourself into collaboration and maintenance of existing code, you can't avoid knowing about it.
If you ever have to debug the code produced by (setf (cdr place) ...), you will likely see rplaca or a similar function.
It is also the reason why Lisps can feel so icky. This is a huge hunk of "macro magic" that can be damn near impenetrable when something goes wrong.
The longer I'm at this the more I'm convinced that macros are the reason why Lisp lost. Even if I concede that macros are hugely powerful, every language that leans into something equivalent to macros winds up with inscrutable errors, debugging issues and/or excessive compile times.
It could be as simple as
(defsetf superdupercar superduperrplaca)
If you have a function (superduperrplaca a x).I think it worked better in the past when people used acronyms and very short names for function and variable names. Today, with self-documenting code style, names are longer for both functions and names. Constantly typing full paths gets annoying quite soon. Code is also more verbose to read and less code fits into 80 columns.
My personal conclusion is that I actually prefer to abstract those away with a proper name like aset, put, and so on, just to make my own code less verbose.
If they really wanted to simplify the language, they should have perhaps removed 'setq' and 'setf' and just kept 'set' in the language but with the powers of setf. Not to mention that users have to learn how to write own setf accessors, unless it is very simple stuff (the extra bit of magic you mention).
As a "simplification" of the language I think they have failed, if that was the goal. That does not mean that 'setf' is not useful. On the contrary, it is a very useful tool to be able to compute and set a "place", just shouldn't be sold as a simplification.
Oh, they are. (SETF FOO) is a valid function name in Lisp - even though it's a list, not a symbol.
CL-USER> (defun (setf foo) (newval) (format t ";; Setting ~S~%" newval) newval)
(SETF FOO)
CL-USER> (setf (foo) 42)
;; Setting 42
42 (setf (buffer-string) "hello")
I thought this was just bananas, turns out that Elisp setf simply has a bunch of hardcoded rules for matching getters/setters for even a lot of non-standard data structures like Buffer that are Emacs specific. So the above gets rewritten to: (insert (prog1 "hello" (erase-buffer)))
It's not exactly AGI but still very cool. (gv-define-setter buffer-string (store)
`(insert (prog1 ,store (erase-buffer))))
Unfortunately, it is marked obsolete since 29.1. The NEWS says:* Many seldom-used generalized variables have been made obsolete. Emacs has a number of rather obscure generalized variables defined, that, for instance, allowed you to say things like:
(setf (point-min) 4)
These never caught on and have been made obsolete. The form above,
for instance, is the same as saying (narrow-to-region 4 (point-max)) (defclass a ()
((var :accessor var)))
(var *some-a-object*) ;; => ok
(defclass b ()
((var :accessor var)))
(var *some-b-object*) ;; => ok
> If we don’t like using car to indicate the head of a list… then we can use `first` (and second, third… up to 9nth).
---
https://lispcookbook.github.io/cl-cookbook/editor-support.ht...
(var *some-object* value)
somewhere far away from the class definition, say in another file, can you tell immediately what that code does? Can you understand it directly by looking at the code? Do you define something? Do you set something or do you just read something like a property list (something like rassoc)? (setf (var *some-object*) value)
With setf it is immediately clear from the code what is going on. I don't know if that is the best example, just thinking of all the arguments for and against setf :). Personally, I find it makes code more verbose in many situations. (var *some-object* value)
because we also use SETF with accessors: (setf (var *some-object*) value)
Quick example: (defclass person ()
((name :accessor name)))
(make-instance 'person)
(setf (name *) "me")Conventional wisdom: "No... no... no... you can't implement an OS kernel in managed code" Lisp machine OS authors: "Hold me beer. [Laughs in 1970s]."
What is wild was invisible pointers ...
A forwarding pointer specifies that a reference to the location containing it should be redirected to another memory location, just as in postal forwarding. These are also called invisible pointers.
(Not a member of the "we wall think we can't implement systems in managed code" club -- there are plenty of such systems dating back to before Unix :-) )Most languages will let you bypass their layering if you want to. But it's usually ultimately a bad idea.
https://www.kylheku.com/cgit/lisp-snippets/tree/refs.lisp [2012-01]
CL still tries to preserve something from lambda calculus.
What in Lisp exactly is similar to lambda calculus, though? Lisp isn't even a pure language.
This part is wrong: (setf a-var) is simply the name of the method.
I never looked too hard at how this was accomplished. Fun article!
That's how all of Common Lisp worked out for me. It all feels strange, then after a couple years of hobbyist usage, it all feels natural.
Suppose we have a pure cons type, call it PCONS, with accessors PCAR and PCDR. Then, the form
(setf (pcar (pcdr x)) y)
turns into (effectively; the actual expansion has temporary variables to avoid duplicating forms)
(progn (setq x (pcons (pcar x) (pcons y (pcdr (pcdr x))))) y)
I think that's the crux, does anyone have an argument for why l should be (1 2 3) instead of (23 2 3)? It breaks referential transparency that you have to substitute the value of h into setf to get it to work.
https://www.lispworks.com/documentation/HyperSpec/Body/m_set...
It seems that OP didn't know what a reference is.
Setf is rather a computation of a reference, than a reference.
A reference in Java and C++ is a pure pointer, with some syntactic sugar in java (you skeep */-> to define and dereference it), and few corns of sugar spread on top of it in C++ (can't be null).
Here are my thoughts on the article:
Firstly, there are errors in the definition of OUR-SETF macro:
1. (symbol-function ,our-setf-function-name) will signal an unbound variable error. ,our-setf-function-name must be quoted: (symbol-function ',our-setf-function-name)
2. Arguments to APPLY are ill-formed. Instead of CONS, LIST must be used.
(apply (symbol-function ',our-setf-function-name)
(cons ,new-value ,@(cdr locator)))
Using CONS, (our-setf (head (list 1 2)) 0) expands to: (apply (symbol-function '|(our-setf head)|)
(cons 0 (list 1 2)))
Which is equivalent to (|(our-setf head)| 0 1 2), clearly not what we want.Furthermore, an OUR-SETF that accepts multiple places will fail. Consider an example from the article:
(our-setf (aref a 23) 0)
It expands to: (|(our-setf aref)| (cons 0 a 23))
Clearly, an error.The correct usage of APPLY is:
(apply (symbol-function ',our-setf-function-name)
(list ,new-value ,@(cdr locator)))
Alternatively, use a FUNCALL: (funcall (symbol-function ',our-setf-function-name)
,new-value ,@(cdr locator))
Also, SYMBOL-FUNCTION can be dropped, as both FUNCALL and APPLY accept a function designator. (funcall ',our-setf-function-name ,new-value ,@(cdr locator))
This concludes the errors part.Secondly, I was really confused by the symbol generation for OUR-SETF example. I thought of function-defining macros, such as DEFUN and DEFMETHOD, and they accept lists of the form (SETF X) and not symbols whose name looks like a list. This latter notation could be better explained by using multiple escape characters from the Common Lisp HyperSpec. For example, the bar character: |(our-setf head)|.
Multiple escape characters also mean that symbol generation is needed only in OUR-SETF macro. Generic functions and methods can be defined directly:
(defgeneric |(OUR-SETF HEAD)| (new-value place))
(defmethod |(OUR-SETF HEAD)| (new-value (place list))
(rplaca place new-value)
new-value)
This would also require changes to OUR-SETF macro because the symbols used to name generic functions and methods are now interned in a package. (defmacro our-setf (locator new-value)
(let* ((selector (car locator))
; use the selector's package,
; selector must be interned
(our-setf-function-name (intern (format nil "(OUR-SETF ~a)"
selector)
(symbol-package selector))))
`(funcall ',our-setf-function-name
,new-value ,@(cdr locator))))
With this change we can even use selectors from other packages.As the author said, these are symbols with weird names. So we can remove most of the weirdness with more macros:
(eval-when (:compile-toplevel :load-toplevel :execute)
(defun selector-symbol (selector)
(or (get selector 'our-setf-name)
(gentemp (string selector) (symbol-package selector)))))
(defmacro defgeneric-setf ((selector &rest selector-params) (new-value))
(let ((name (selector-symbol selector)))
`(progn
(setf (get ',selector 'our-setf-name) ',name)
(defgeneric ,name (,new-value ,@selector-params)))))
(defmacro defmethod-setf ((selector &rest selector-params) (new-value) &body body)
(let ((name (selector-symbol selector)))
`(progn
(setf (get ',selector 'our-setf-name) ',name)
(defmethod ,name (,new-value ,@selector-params)
,@body))))
(defmacro our-setf ((selector &rest selector-params) new-value)
(let ((our-setf-function-name (selector-symbol selector)))
`(funcall ',our-setf-function-name ,new-value ,@selector-params)))
(defgeneric-setf (head x) (new-value))
(defmethod-setf (head (x cons)) (new-value)
(rplaca x new-value)
new-value)
#+nil
(let ((xs (list 1 2)))
; expands to (funcall 'headN 0 xs) where N is a number from GENTEMP
(our-setf (head xs) 0)
xs) ; => (0 2)
(defgeneric-setf (our-elt seq idx) (new-value))
(defmethod-setf (our-elt (seq list) idx) (new-value)
(loop for i from 0 to idx
for cons on seq
finally (our-setf (head cons) new-value))
new-value)
#+nil
(let ((xs (list 'a 'b 'c)))
; expands to (funcall 'our-eltN 'k xs 1) where N is a number from GENTEMP
(our-setf (our-elt xs 1) 'k)
xs) ; => (a k c)Then I saw it was about LISP, which I don't know/use and got a little bummed out.
Now maybe my mental model of self is wrong and I need to learn LISP and create a mental model for setf and that will lead me to an awakening of a correct mental model of self?
https://en.wikipedia.org/wiki/Self_%28programming_language%2...
"It's not that you're not real. We all think we're real, and that's not wrong. You are real. But you think you're really real — you exaggerated."
Could lead to an awakening of the psychological reality of Lisp: https://web.archive.org/web/20180727081543/http://www.pgc.co...
Often I just get "huh, that's neat, now let's get back to mining the coalface".
This particular post is better than most, and seems like genuine interest, and targeted at people already in the fold, which is fine. Though probably no one else is going see it and think "I gotta get me some of that."
The intentional advocacy posts, on the other hand... I usually don't see them appealing well to even the minority of programmers who are amenable to a low-employability platform. While they seemingly help to keep the platform low-employability, by making a weak pitch in the moment that someone was curious/bored enough to look at that link.
and start debugging a misbehaving compiler macro used everywhere in a 1500-line multi-nested LOOP.
Claiming that “getting out of bed and programming in anything other than a lisp makes life less worth living” is at best as extremely immature and strange stance to take.
Edit: upon further reflection, this whole “lisp evangelism” is pretty tired. It’s not some magic bullet. Idgaf if paul goddamn graham knows how to write code in lisp. That means basically nothing, at all.
Sometimes people get offended when we give them a fully working awk/sed one-liner that does the job better but, uh, that's what we would have used ourselves!
(or "that's seriously computation heavy, use C/Rust/Julia/etc. because much though we love the perl5 VM for quick scripts and large-scale OO business logic apps this is not going to work nicely and trying to force perl to do it will be a rathole")
Though I do sort of miss my 'green threads via call/cc in guile sitting atop an ancient but functional perl event loop' system, it was ~20 years ago now and I'm not suggesting anybody else would ever want to use it, but in some ways it was still nicer than modern async/await style event loop driving nonetheless.
(yes, I'm an outlier and should not be counted)
There's quite the overlap between perlers and lispers because of (among other things) the shared love of 'building the language up towards the problem.'
People have been known to describe bits of my perl code as easiest read by treating it as lisp that incidentally uses a really weird sort of M-expressions and runs on the perl5 VM ;)
https://github.com/CodyReichert/awesome-cl
Thinking about recent feedback:
- https://blog.funcall.org//lisp%20psychoacoustics/2024/05/01/... (https://news.ycombinator.com/item?id=40233736) (2024) - https://news.ycombinator.com/item?id=33467269 (2022) "Common Lisp was a conscious decision because it allows a small team to be incredibly productive, plus the fact that it's a live image allows you to connect to it over the internet and poke and prod the current state, which has really allowed a much clearer understanding of the data." - https://lisp-journey.gitlab.io/blog/lisp-interview-kina/ (2020)
(Is something like Numba not a spiritual Lisp macro?)
Slow, bizarre in places, no live image though I can at least trivially replace function implementations and add methods to running code, binary building sucks, and there is of course a whole ass list of things that are uniquely awful (assume that the longest list you've seen from somebody who hates the language has 50% overlap with my personal list and that my list is probably longer) but ... it works. For me, at least.
I loved scheme; I keep picking at common lisp getting slowly better at thinking in terms of the capabilities of a system that's image based and deeply condition-system-ed, and I hope to get there eventually :D
A good one that I think is fairly typical of my annoyances - 'each' - the each keyword uses an iterator attached to the data structure it's iterating, so if you return or throw part way through doing so the iterator is left half way through and the next attempt at iteration will ... not do what you thought it would.
The reason for this is that 'each' was originally introduced back in the mists of time to allow iteration over a dictionary (hash) backed by a DBM file of some sort, and those files only -permitted- a single iterator at the same time.
Of course, people then started to use it for other things, but by that point making it behave a less surprising way for that would've broken existing code that used it for its original purpose (Larry and I chatted about this once and our conclusions mostly came down to 'alas').
Backwards compatibility is a hard master, and perl (and most cpan modules) work very hard not to break it, which leaves assorted things like that lying around.
My 'solution' is to accept this as fact and simply not use 'each', instead iterating over the keys and pulling the values out in the loop body, and honestly that's fine for me but it's moderately annoying having to keep telling newbies they need to pretend the keyword doesn't exist.
Things that freenode/libera #perl have found to be footguns often enough that we regularly teach people not to do that are collected in https://metacpan.org/pod/Perl::Critic::Community so you can find them with static analysis (and yes, of -course- each is on that list :) and might also be enlightening.
(I personally quite like using the behaviour covered by ConditionalImplicitReturn, but that one probably still belongs on the list because I suspect the vast majority of the time it gets used by accident and becomes a footgun with a delay fuse ... i.e. "if you know enough to use this safely, you should also know enough to turn that rule off" applies)
Part of the trouble I'd have producing a comprehensive such list is that so much of this knowledge has been baked into my brain/fingers long enough that there's not really any thought involved in avoiding triggering them anymore - my primary reason for mentioning it was "I am absolutely not blind to its faults, though every language has some somewhere" because I wanted to explain why I like perl overall without sounding like an Evangelism Task Force member.
But yeah, if you want to point me in a direction I'm still happy to dig through my brain and see if I can find you a representative example :)
The computer thinks imperatively. I prefer to be on common ground.