3 karma · joined October 17, 2024
(I'm not going full Richard Stallman about it, sometimes I'll suffer the ads for content, I throw many articles into archive.is, which only shows one ad at the bottom, and I think it's worthwhile to support them, etc.)
OP is a very long book, I haven't had a chance to read it fully, but first thing I wanted to see is how they manage message sending, and it's as jank as it is in flavors. I thought considering how custom interlisp can be they'd do something special. nope, it's just send.
for those who don't know what I'm talking about, an old school smalltalk style object system lets one send arbitrary messages, without prior knowledge of what those messages might be, and treats the receiving object as a blackbox (conceptually anyway). this approach doesn't map well to s-exp, because first symbol in an s-expression drives the logic. in flavors (and in LOOPS) the symbol used is "SEND", so in order to actually send a message you write something like
(send some-window :set-edge 10 10 40 40)
as you can imagine a very heavily object oriented code becomes littered with sends. LOOPS seems to make it a little bit less painful by making ← an equivalent of send, so above can be written as (← SomeWindow SetEdge 10 10 40 40)
this is obviously only a margin improvement.clos solved this problem by drifting away from smalltalk's blackbox concept and making everything generic function oriented,
(set-edge some-window 10 10 40 40)for example a useful datatype is an association list. (setq x ((a . 1) (b . 2) (c . nil))) you can query it by calling (assoc 'a x) which is going to give you back a cons cell (a . 1) in this case. now the presence or absence of this cell indicates the association. if you want to know explicitly that C is nil, then you have an option to, and it's similar in function call counts to Scheme. if you don't care though about the distinction you can do (cdr (assoc 'a x)) which is going to give you 1. doing (cdr (assoc 'foo x)) will give you nil without erroring out. it's a pretty common pattern.
in case of established data types like association list, you will probably have a library of useful functions already defined, like you can write your own getassoc function that hides the above. you can also return multiple values from getassoc the same way as gethash does the first value being the value, and the second value being whether or not there's a corresponding cons cell.
but when you define your own adhoc cons cell based structures, you don't have the benefit of predefined functions. so let's say you have an association list of symbols to cons cells (setq x ((a . (foo . 1)) (b . (bar . 2)) (c . nil))). if I want to get foo out of that list, I'll say (cadr (assoc x 'a)) which will return foo. doing (cadr (assoc x 'c)) or (cadr (assoc x 'missing)) will both return nil. these later manipulations require extensive scaffolding in Scheme.
(defun explode (x)
(mapcar (lambda (x)
(intern (char-to-string x)))
(string-to-list (prin1 x))))
turning character into symbol seems natural, because then you are reducing your needed function space even more. I'm surprised the original operated on prin1 output, not sure what the logic behind that is. on a lisp machine (zl:explode "foo") gives me '(|"| |f| |o| |o| |"|)