TE
TechEcho
Home24h TopNewestBestAskShowJobs
GitHubTwitter
Home

TechEcho

A tech news platform built with Next.js, providing global tech news and discussions.

GitHubTwitter

Home

HomeNewestBestAskShowJobs

Resources

HackerNews APIOriginal HackerNewsNext.js

© 2025 TechEcho. All rights reserved.

My mental model of setf was wrong

172 pointsby nemoniac10 months ago

17 comments

lispm10 months ago
My basic mental model of SETF was always relatively simple. We were told that in university, many decades ago.<p>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 (-&gt; Maclisp) or GETPROP (-&gt; Interlisp). The setter was PUTPROP.<p>So for any datastructure we need to learn both a getter and the corresponding setter.<p>With SETF in Common Lisp we only need to know the getter. SETF <i>knows</i> for each getter a corresponding setter.<p>So for a datastructure FOO we don&#x27;t need to know both GETBAR and BARSET!.<p>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.<p>Advantage: we don&#x27;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.<p>In reality there is a bit more magic involved.
评论 #41105911 未加载
评论 #41102784 未加载
phoe-krk10 months ago
<i>&gt; But the names setf synthesises aren’t recognised by defun and defgeneric</i><p>Oh, they are. (SETF FOO) is a valid function name in Lisp - even though it&#x27;s a list, not a symbol.<p><pre><code> CL-USER&gt; (defun (setf foo) (newval) (format t &quot;;; Setting ~S~%&quot; newval) newval) (SETF FOO) CL-USER&gt; (setf (foo) 42) ;; Setting 42 42</code></pre>
natrys10 months ago
My Lisp education was mostly from Elisp, but this I understood early when I was reading some random code and came across:<p><pre><code> (setf (buffer-string) &quot;hello&quot;) </code></pre> I thought this was just bananas, turns out that Elisp setf simply has a bunch of hardcoded rules for matching getters&#x2F;setters for even a lot of non-standard data structures like Buffer that are Emacs specific. So the above gets rewritten to:<p><pre><code> (insert (prog1 &quot;hello&quot; (erase-buffer))) </code></pre> It&#x27;s not exactly AGI but still very cool.
评论 #41103556 未加载
vindarel10 months ago
nitpicking, but I am not a fan of the naming. No need of a capital &quot;A&quot; and no need to recall the class name in the accessor names: &quot;var&quot; instead of &quot;a-var&quot; is enough, since yes, we can very well have an accessor &quot;var&quot; for a class &quot;b&quot;. &quot;var&quot; is a generic function. So:<p><pre><code> (defclass a () ((var :accessor var))) (var *some-a-object*) ;; =&gt; ok (defclass b () ((var :accessor var))) (var *some-b-object*) ;; =&gt; ok </code></pre> &gt; If we don’t like using car to indicate the head of a list<p>… then we can use `first` (and second, third… up to 9nth).<p>---<p><a href="https:&#x2F;&#x2F;lispcookbook.github.io&#x2F;cl-cookbook&#x2F;editor-support.html" rel="nofollow">https:&#x2F;&#x2F;lispcookbook.github.io&#x2F;cl-cookbook&#x2F;editor-support.ht...</a><p><a href="https:&#x2F;&#x2F;github.com&#x2F;CodyReichert&#x2F;awesome-cl">https:&#x2F;&#x2F;github.com&#x2F;CodyReichert&#x2F;awesome-cl</a>
评论 #41105958 未加载
QuesnayJr10 months ago
I have sometimes wished that &quot;locations&quot; in Common Lisp were real lvalues, rather than an illusion made possible by macros.
评论 #41100674 未加载
评论 #41101321 未加载
killerstorm10 months ago
&gt; This is a method specialised twice: on the type of an argument (A), and the selector used to within the locator (a-var).<p>This part is wrong: (setf a-var) is simply the name of the method.
taeric10 months ago
I don&#x27;t know why setf always felt hard for me to really grok back when I was starting programming. It has grown to feel extremely natural. Is nice to know that for most &quot;accessor&quot; style functions, you can use those in setf to update their value.<p>I never looked too hard at how this was accomplished. Fun article!
评论 #41101640 未加载
ahefner10 months ago
Curious writing an article about how setf works and not mentioning &#x27;defsetf&#x27;, only setf methods. I suspect the gentleman&#x27;s mental model of setf remains slightly wrong.
pfdietz10 months ago
One interesting thing one can do with SETF is define expanders that dive into accessors on &quot;pure&quot; datatypes, copying as needed.<p>Suppose we have a pure cons type, call it PCONS, with accessors PCAR and PCDR. Then, the form<p>(setf (pcar (pcdr x)) y)<p>turns into (effectively; the actual expansion has temporary variables to avoid duplicating forms)<p>(progn (setq x (pcons (pcar x) (pcons y (pcdr (pcdr x))))) y)
评论 #41105966 未加载
Mathnerd31410 months ago
&gt; You can’t write code like this: (let* ((l (list 1 2 3)) (h (car l))) (setf h 23))<p>I think that&#x27;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.
lupire10 months ago
setf is like referring to a mutable reference in C++ or Java.<p><a href="https:&#x2F;&#x2F;www.lispworks.com&#x2F;documentation&#x2F;HyperSpec&#x2F;Body&#x2F;m_setf_.htm" rel="nofollow">https:&#x2F;&#x2F;www.lispworks.com&#x2F;documentation&#x2F;HyperSpec&#x2F;Body&#x2F;m_set...</a><p>It seems that OP didn&#x27;t know what a reference is.
评论 #41106013 未加载
homedirectory10 months ago
I also once thought that the magic underlying SETF has access to true L-values, and had also been surprised by it being macros under the hood.<p>Here are my thoughts on the article:<p>Firstly, there are errors in the definition of OUR-SETF macro:<p>1. (symbol-function ,our-setf-function-name) will signal an unbound variable error. ,our-setf-function-name must be quoted: (symbol-function &#x27;,our-setf-function-name)<p>2. Arguments to APPLY are ill-formed. Instead of CONS, LIST must be used.<p><pre><code> (apply (symbol-function &#x27;,our-setf-function-name) (cons ,new-value ,@(cdr locator))) </code></pre> Using CONS, (our-setf (head (list 1 2)) 0) expands to:<p><pre><code> (apply (symbol-function &#x27;|(our-setf head)|) (cons 0 (list 1 2))) </code></pre> Which is equivalent to (|(our-setf head)| 0 1 2), clearly not what we want.<p>Furthermore, an OUR-SETF that accepts multiple places will fail. Consider an example from the article:<p><pre><code> (our-setf (aref a 23) 0) </code></pre> It expands to:<p><pre><code> (|(our-setf aref)| (cons 0 a 23)) </code></pre> Clearly, an error.<p>The correct usage of APPLY is:<p><pre><code> (apply (symbol-function &#x27;,our-setf-function-name) (list ,new-value ,@(cdr locator))) </code></pre> Alternatively, use a FUNCALL:<p><pre><code> (funcall (symbol-function &#x27;,our-setf-function-name) ,new-value ,@(cdr locator)) </code></pre> Also, SYMBOL-FUNCTION can be dropped, as both FUNCALL and APPLY accept a <i>function designator</i>.<p><pre><code> (funcall &#x27;,our-setf-function-name ,new-value ,@(cdr locator)) </code></pre> This concludes the errors part.<p>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)|.<p>Multiple escape characters also mean that symbol generation is needed only in OUR-SETF macro. Generic functions and methods can be defined directly:<p><pre><code> (defgeneric |(OUR-SETF HEAD)| (new-value place)) (defmethod |(OUR-SETF HEAD)| (new-value (place list)) (rplaca place new-value) new-value) </code></pre> 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.<p><pre><code> (defmacro our-setf (locator new-value) (let* ((selector (car locator)) ; use the selector&#x27;s package, ; selector must be interned (our-setf-function-name (intern (format nil &quot;(OUR-SETF ~a)&quot; selector) (symbol-package selector)))) `(funcall &#x27;,our-setf-function-name ,new-value ,@(cdr locator)))) </code></pre> With this change we can even use selectors from other packages.<p>As the author said, these are symbols with weird names. So we can remove most of the weirdness with more macros:<p><pre><code> (eval-when (:compile-toplevel :load-toplevel :execute) (defun selector-symbol (selector) (or (get selector &#x27;our-setf-name) (gentemp (string selector) (symbol-package selector))))) (defmacro defgeneric-setf ((selector &amp;rest selector-params) (new-value)) (let ((name (selector-symbol selector))) `(progn (setf (get &#x27;,selector &#x27;our-setf-name) &#x27;,name) (defgeneric ,name (,new-value ,@selector-params))))) (defmacro defmethod-setf ((selector &amp;rest selector-params) (new-value) &amp;body body) (let ((name (selector-symbol selector))) `(progn (setf (get &#x27;,selector &#x27;our-setf-name) &#x27;,name) (defmethod ,name (,new-value ,@selector-params) ,@body)))) (defmacro our-setf ((selector &amp;rest selector-params) new-value) (let ((our-setf-function-name (selector-symbol selector))) `(funcall &#x27;,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 &#x27;headN 0 xs) where N is a number from GENTEMP (our-setf (head xs) 0) xs) ; =&gt; (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 &#x27;a &#x27;b &#x27;c))) ; expands to (funcall &#x27;our-eltN &#x27;k xs 1) where N is a number from GENTEMP (our-setf (our-elt xs 1) &#x27;k) xs) ; =&gt; (a k c)</code></pre>
rambojohnson10 months ago
ah well, live and learn!
guhcampos10 months ago
Somehow I read this as &quot;my mental model of self was wrong&quot; and it totally resonated with me.<p>Then I saw it was about LISP, which I don&#x27;t know&#x2F;use and got a little bummed out.<p>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?
评论 #41101582 未加载
评论 #41103310 未加载
评论 #41103908 未加载
评论 #41101480 未加载
评论 #41101461 未加载
评论 #41101576 未加载
waynecochran10 months ago
Kramer once asked George “do you ever yearn?” Well I yearn. Every time I see a post on common lisp I yearn to have a job where I do nothing but mathematics and realize solutions in common lisp. Unfortunately I have to get out of bed now and deal with janky code written by someone who can only think imperatively.
评论 #41100819 未加载
评论 #41101172 未加载
评论 #41100800 未加载
评论 #41100965 未加载
评论 #41101324 未加载
评论 #41106116 未加载
评论 #41101961 未加载
评论 #41100662 未加载
scoot10 months ago
I misread the title as &quot;My mental model of self was wrong&quot;, and was expecting a very different article!
评论 #41100904 未加载
评论 #41100988 未加载
blacklion10 months ago
Am I only who read title as &quot;My mental model of <i>self</i> was wrong&quot;? And thought it is about psychology, not about Self programming language.
评论 #41103838 未加载