9 ms·
My mental model of setf was wrong
- homedirectory 2y agoI 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. 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)
- QuesnayJr 2y agoI have sometimes wished that "locations" in Common Lisp were real lvalues, rather than an illusion made possible by macros.
- reikonomusha 2y agoCL-LOCATIVES [1] make these first-class values. It essentially turns any setf-able place into a mutable pointer. [1]: https://github.com/stylewarning/cl-locatives https://github.com/stylewarning/cl-locatives
- quotemstr 2y agoYou know, what's wild is that Lisp machines had kernels and garbage collectors implemented in Lisp too, making heavy use of locatives as raw pointers to implement basic system functions like writing to IO devices and doing garbage collection. It's wild that we all think the primitive -> managed stack (ASM, then C/Rust/C++/etc., then Java/C#/Python on top) is somehow an intrinsic property of computation, but there are multiple existence proofs (of which the lisp machine is one) of whole computing stacks implemented in managed code and working just fine. 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]."
- amszmidt 2y agoMost Lisp systems, be it Lisp Machines, or modern Common Lisp implementations do the same. The Lisp Machine didn't really have a kernel though, unless you call the microcode that implemented the Lisp Machine macrocode "engine". As for I/O, that was just memory mapped .. you write/read to some magic number. 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 :-) )
- 2y ago
- waynecochran 2y agoKramer 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.
- deleted 2y ago[deleted]
- AeroNotix 2y agoI feel you. I've written quite a lot of Common Lisp over the years and I've tried to show off its capabilities to co-workers, often in comparison to the language used that that job. Often I just get "huh, that's neat, now let's get back to mining the coalface".
- neilv 2y agoI have a suspicion that most Lisp articles we see on HN reinforce the perception that this is something other people shouldn't take seriously, much less touch. 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.
- mgsouth 2y agoThen one day you really wake up, and realize you do have that job. You hop out of bed with a spring in your step, walk into your office, sit down at your desk... and start debugging a misbehaving compiler macro used everywhere in a 1500-line multi-nested LOOP.
- Capricorn2481 2y ago
- scoot 2y agoI misread the title as "My mental model of self was wrong", and was expecting a very different article!
- xanderlewis 2y agoI only realised it didn’t say that after reading this comment.
- tome 2y agoMe too. I skimmed through the comments assuming they were talking about the Lisp equivalent of Python's self.
- deleted 2y ago[deleted]
- taeric 2y agoI don'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 "accessor" style functions, you can use those in setf to update their value. I never looked too hard at how this was accomplished. Fun article!
- martinflack 2y ago> It has grown to feel extremely natural. 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.
- taeric 2y agoI wish I knew how to communicate that change to folks. There is a lot of history on many of the forms that exist in common lisp. And much of it is far more practical in origin than I expected going into it.
- vindarel 2y agonitpicking, but I am not a fan of the naming. No need of a capital "A" and no need to recall the class name in the accessor names: "var" instead of "a-var" is enough, since yes, we can very well have an accessor "var" for a class "b". "var" is a generic function. So: (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.html https://lispcookbook.github.io/cl-cookbook/editor-support.ht... https://github.com/CodyReichert/awesome-cl https://github.com/CodyReichert/awesome-cl
- amno 2y agoI am with you, I think setf is making code repetitive and more verbose in many cases, however in this particular case, if you see (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.
- vindarel 2y agoSince I was talking about accessors / generic functions, I don't get the point of this example: (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")
- phoe-krk 2y ago> But the names setf synthesises aren’t recognised by defun and defgeneric 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
- guhcampos 2y agoSomehow I read this as "my mental model of self was wrong" and it totally resonated with me. 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?
- monarchwadia 2y agoAm also similarly bummed :)
- layer8 2y agoSounds like your mental model of the title was wrong. ;)
- bryanrasmussen 2y agoread the same, I interpreted it as failing eyesight and tiredness.
- msla 2y agoI think you need to learn Smalltalk to have a mental model of Self: https://en.wikipedia.org/wiki/Self_%28programming_language%29 https://en.wikipedia.org/wiki/Self_%28programming_language%2...
- cypherpunks01 2y agoSame. I was reminded of a quote I like from a Mongolian Buddhist lama: "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."
- killerstorm 2y ago> This is a method specialised twice: on the type of an argument (A), and the selector used to within the locator (a-var). This part is wrong: (setf a-var) is simply the name of the method.
- blacklion 2y agoAm I only who read title as "My mental model of self was wrong"? And thought it is about psychology, not about Self programming language.
- romamark 2y ago[flagged]
- lispm 2y agoMy basic mental model of SETF was always relatively simple. We were told that in university, many decades ago. 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.
- jmount 2y agoWow that is a great description of awful design choices. The purpose of SETF is avoiding learning the language?
- chmod775 2y agoLanguage features are superseded by better ones all the time. Nobody gets language design correct on the first try, especially when requirements change over time and by application. If you look at the history of languages like JS or B/C/etc. you better strap in for a crazy ride.
- Zambyte 2y agoThe purpose is to avoid learning a large vocabulary in favor of a smaller, more general vocabulary. That is abstraction, and it's a feature of the language, so you can't learn it while avoiding learning the language.
- smegsicle 2y agothe purpose of setf is to provide an intuitive, powerful, extensible, and simple to use abstraction for an extremely common operation without introducing any new syntax
- natrys 2y agoMy Lisp education was mostly from Elisp, but this I understood early when I was reading some random code and came across: (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.
- michaelhoffman 2y agoThis is not "hard-coded" as I would describe it. It is defined in `gv.el`: (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))
- loasn 2y ago[dead]
- pfdietz 2y agoOne interesting thing one can do with SETF is define expanders that dive into accessors on "pure" datatypes, copying as needed. 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)
- amno 2y agoWe need nsetf! :-)
- lupire 2y agosetf is like referring to a mutable reference in C++ or Java. https://www.lispworks.com/documentation/HyperSpec/Body/m_setf_.htm https://www.lispworks.com/documentation/HyperSpec/Body/m_set... It seems that OP didn't know what a reference is.
- amno 2y ago> setf is like referring to a mutable reference in C++ or Java. 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).
- ahefner 2y agoCurious writing an article about how setf works and not mentioning 'defsetf', only setf methods. I suspect the gentleman's mental model of setf remains slightly wrong.
- Mathnerd314 2y ago> You can’t write code like this: (let* ((l (list 1 2 3)) (h (car l))) (setf h 23)) 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.
- rambojohnson 2y agoah well, live and learn!