đŸ Archived View for idiomdrottning.org âș mdg captured on 2022-07-16 at 13:56:38. Gemini links have been rewritten to link to archived content
âŹ ïž Previous capture (2021-11-30)
âĄïž Next capture (2023-01-29)
-=-=-=-=-=-=-
A define that can destructure, pattern match, be nested, and create generics!
(define (foop so much #!key (insight 2) (joy 3)) (list so much insight joy)) (define (foop (? string? apples) (? string? horses)) (string-append horses apples)) (list (foop "way" "no ") (foop 3 2 joy: 1))
â (âno wayâ (3 2 2 1))
It works.â„
When thereâs just a normal define (with or without DSSSL stuff), it expands to a normal define. Using that variable or calling that function doesnât have any overhead, itâs just bound normally.
Then when you define more stuff, it changes to become a dispatching generic multimethod.
One normal define becomes a normal define. It can have DSSSL stuff such as keyword arguments, thatâs fine.
Two defines (normal or not), or one un-normal define (using destructuring for example), and it switches to matching.
(define (just-a-single (oh my what)) (list my what oh)) (just-a-single (string->list "hey"))
â (#\e #\y #\h)
Here is how the my-map example Iâve been using looks like in this system:
(define (my-map proc (x . xs)) (cons (proc x) (my-map proc xs))) (define (my-map proc ()) '()) (my-map add1 '(1 2 3))
â (2 3 4)
LIFO baby! Most recently defined is checked first. So put the fall backs first and then go more specific. This is a deliberate reversal of match-lambda since it lets you define special cases later.
Nesting in the car position (for currying purps) works fine, but they must all look the same. You canât destructure in there, only in the outermost position a.k.a. the cdr position a.k.a. the args. The car positions must be exactly the same (their code/âsymbolic/âread representation must be equal?) including variable names.
The older ones arenât thrown away, exactly. The most recent one and everything older that is equal? are what counts. (This isnât slow, it uses a hash-table to sort that out.)
(define ((foo a) b c d) (* a b c d)) (define ((foo a b) c) (list a b c)); â This one won't count (define ((foo a) b c) (+ a b c)); â because this one came after (list ((foo 3) 2 3) ((foo 3) 2 3 8))
â (8 144)
If you just define a plain vanilla variable, then thereâs not gonna be any dispatching there:
(define horses 23)
Any older stuff you had stored in horses isnât lost, itâll be ready for when you define a new generic using their same car signature.
The same goes for
(define horses (lambda () 23))
Thatâs not gonna be part of a generic.
âSingle variablesâ like this arenât saved, and can be overwritten on next call to define.
âOMG, I sent a mistyped definition to the REPL and now I canât define over it since this new define just adds stuff!â
This doesnât work:
(define (wrong tyop) typo) (define wrong #f) (define (wrong typo) typo)
Itâll have both the tyop and the typo version in there.
I mean, thatâd kind of be fine in this particular case since the dispatcher will see the latest version first and that happens to shadow the typos, but, if you put typos in predicates or whatever you might be in for a rough time.
However, to reset when you mistype something and wanna re-define at the REPL, do (define reset: <the-name>), so in this example
(define reset: wrong)
And to get access to the entire underlying call-table* for your own metamagic and hackery purps just use (define).
We wanna make easy things easy but any thing possible.â„
Emacs nerds, here is an elisp function that resets a define youâre in:
(defun mdg-reset-define () (interactive) (save-excursion (re-search-backward "^(define ") (forward-char 1) (re-search-forward "(") (let* ((end (1- (re-search-forward "[ )]"))) (beg (1+ (re-search-backward "("))) (str (buffer-substring beg end))) (comint-send-string (scheme-proc) (format "(define reset: %s)\n" str)) (message "Resetting %s" str))))
Bind it to whatever. You then need to re-send the sexps you do want.
You canât combine DSSSL (i.e. #!key, #!optional and #!rest) and matchable in the same line, but, as you saw in the foop example above, the same generic can handle both DSSSL stuff and matchable just fine, in separate clauses.
The dispatcher sees all of that stuff as just one big . rest tail so it catches and collides with a lot of patterns. So be careful if you wanna combine DSSSL and generics.
This define is available under the name define-dx in the match-generics egg.
git clone https://idiomdrottning.org/match-generics
Inside of the brev repo, and provided with the big brev egg, there is an extension named mdg that imports it under the name define and the OG define as define-og. So just (import brev mdg), which is now part of the little brev compiler shell wrapper and is prompted for by the brev2scm conversion process. Or if you want just the define and not the rest of the brev batteries,
(import (rename match-generics (define-dx define)))