- Joined
- Feb 9, 2013
It's really a valuable exercise to engage in.Fantastic post @Marvin . Lays bare the actual mechanics. Reminds me of seeing likewise in C. Too many OOP pros are functionally cargo cultists who couldn't write an OOP implementation, and it shows.
Once you've written it once, you can still use all the convenience macros but you'll never be confused about what's going on under the hood or why certain methods aren't getting called in the right order or whatever.
My implementation was attempting to exactly clone the logic of Chicken Scheme's COOPS, because that was my first exposure to it. Guile's GOOPS works similarly.
When I do my day-job work in Python or Go or whatever, I have a mental model that's my best "guess" about how their inheritance / method calling algorithms work and sometimes I'm fuzzy with it, but I can quickly whip up a test script to test my mental model. Like don't get me wrong, I've read the documentation or whatever, but sometimes I encounter weird inheritance behavior that I'm not expecting, but I know enough about how it could possibly work, that I'm able to make intelligent guesses and test them.
A lot of programmers nowadays have to drop everything and go to Stack Overflow to ask.
The macros themselves are pretty simple, just wrappers around procedural functions that do the heavy lifting.This is genuinely fascinating stuff. I haven't worked on a production Lisp code base but I have some intuitive guesses on how I'd define your macros likedefine-classanddefine-macro. And it's gotten me to thinking about what a mistake it is, that we don't weed out people who are too dumb to get recursion more quickly.![]()
BTW they're all written in the low level Scheme macros. There was a push in the Scheme world to have completely "safe" macro systems that can't accidentally capture or accidentally shadow unwanted variables. And there's lots of super useful macros that can be written in them. But at the end of the day, sometimes you really do want to deliberately create new variables or shadow variables, so any good Scheme needs to still come with some form of low level macros.
Here's a comment from one of the macros:
Code:
;; (define-method (foo qualifier (arg1 typea) (arg2 typeb) arg3 . arg4)
;; body ...)
;; =>
;; (define foo
;; (update-wrapped-procedure!
;; (ensure-wrapped-generic 'foo '(+ 3))
;; 'qualifier
;; (make-method
;; 'arg-count '(+ 3)
;; 'arg-classes (list typea typeb)
;; 'func (lambda (arg1 arg2 arg3 . arg4) body ...)
;; )
;; )
;; )
ensure-wrapped-generic call either grabs the existing one or inserts one in the generic table.Then
update-wrapped-procedure! call updates that generic to add the new method specialized on the classes in the define-method macro.There's some other details, like there's a feature in COOPS where you can specialize on only the first X arguments and leave the rest as normal variables, which is why I have either a static number of typed arguments, or as above, three (plus some untyped variables) as
(+ 3).The qualifier refers to how COOPS (and other CLOS clones) permit before, after and around methods. So you can have code that runs before/after/around the main call. I haven't needed much use for that, but I guess can use it to set up and tear down state around the main call.
If that qualifier isn't provided there's just the default normal call qualifier.
Edit: Oh yeah, and totally re: recursion. If you can't handle recursion, you probably aren't really the best candidate for a career in programming. Even the most basic asm code can make use of recursive algorithms at times.
And in general, it's a mark of having the basic level of mental swiftness to be able to handle these concepts in general, to be a competent computer programmer.
Edit #2: Lol looking at my old repo, I first wrote this like 9 years ago.