Changeset 40390 in project

08/25/21 06:15:37 (4 weeks ago)

Swap out documentation completely

1 edited


  • wiki/eggref/5/match-generics

    r40134 r40390  
    1 == match-generics
     2== The fanciest define of all time
     4A define that can destructure, pattern match, be nested, and create generics!
    4 Here are some flexible and fast generics.
     6 (define (foop so much #!key (insight 2) (joy 3)) (list so much insight joy))
     7 (define (foop (? string? apples) (? string? horses)) (string-append horses apples))
     8 (list (foop "way" "no ") (foop 3 2 joy: 1))
    6 They don't need to have the same number of arguments or use the same
    7 names:
     10⇒ ("no way" (3 2 2 1))
    9  (define-generic (frotz a b c) (list b a c))
    10  (define-generic (frotz x y) (+ x y))
    11  (list (frotz 1 2 3) (frotz 4 5))
     12It works.♥
    13 â‡’ ((2 1 3) 9)
     14When there's just a normal define (with or without DSSSL stuff), it
     15expands to a normal define. Using that variable or calling that function doesn't have any overhead, it's just bound normally.
    15 They can even destructure their arguments:
     17Then when you define more stuff, it changes to become a dispatching generic multimethod.
    17  (define-generic (my-map proc (x . xs))
    18    (cons (proc x) (my-map proc xs)))
     19One normal define becomes a normal define. It can have DSSSL stuff such as keyword arguments, that's fine.
     21Two defines (normal or not), or one un-normal define (using destructuring for example), and it switches to matching.
     23 (define (just-a-single (oh my what)) (list my what oh))
    20  (define-generic (my-map proc ()) '())
     25 (just-a-single (string->list "hey"))
     27⇒ (#\e #\y #\h)
     29Here is how the {{my-map}} example I've been using looks like in this system:
     31 (define (my-map proc (x . xs)) (cons (proc x) (my-map proc xs)))
     32 (define (my-map proc ()) '())
    2233 (my-map add1 '(1 2 3))
    2435⇒ (2 3 4)
    26 To use predicates, use the form {{(? pred var)}}, like this:
    28  (define-generic (plus (? list? a) (? list? b)) (append a b))
    30  (define-generic (plus (? string? a) (? string? b)) (string-append a b))
    32  (define-generic (plus (? number? a) (? number? b)) (+ a b))
    34  (list
    35   (plus "13" "14")
    36   (plus 7 22)
    37   (plus 13 19)
    38   (plus '(a b c) '(1 2 3)))
     38=== Lovable quirks
    40 â‡’ ("1314" 29 32 (a b c 1 2 3))
    42 You need to define them in order from least specific to most specific,
    43 i.e. define the most specifics last and the fallbacks first.
    45 You can even use nested defines! But if you do, each need to use the
    46 same car positions. Only the cdr position (a.k.a the arguments) is
    47 generic.
    49  (define-generic ((frobnicate a) b z) (list a b z))
    50  (define-generic ((frobnicate a) b) (string-append a b))
    52  (let ((hoho (frobnicate "hoho")))
    53    (list (hoho 1 2)
    54          (hoho " and such")))
    56 â‡’ (("hoho" 1 2) "hoho and such")
     40==== Precedence order
     41LIFO baby! Most recently defined is checked first.
    59 === Implementation
    60 By now, maybe some of the long-time readers in the audience has
    61 guessed what is going on.
     44==== Nesting in my car
     45Nesting in the car position (for currying purps) works fine, but they
     46must all look the same. You can't destructure in there, only in the
     47outermost position a.k.a. the cdr position a.k.a. the args. The car
     48positions must be exactly the same (their code/​symbolic/​read
     49representation must be {{equal?}}) including variable names.
    63 Each generic is defined using a clause from [[|matchable]]!
     51The older ones aren't thrown away, exactly. The most recent one and
     52everything older that is equal? are what counts. (This isn't slow, it
     53uses a hash-table to sort that out.)
    65 The implementation using some miscellaneous [[|brev-separate]] stuff is just
 '''five lines!'''
     55 (define ((foo a) b c d) (* a b c d))
     56 (define ((foo a b) c) (list a b c)); ← This one won't count
     57 (define ((foo a) b c) (+ a b c)); ← because this one came after
     59 (list ((foo 3) 2 3) ((foo 3) 2 3 8))
    67  (define-for-syntax gentable (call-table*))
    68  (define-ir-syntax*
    69    (define-generic (name . pattern) . body)
    70    (cons 'match-define
    71          (gentable (strip-syntax name) (cons (cons name pattern) body)))))
     61⇒ (8 144)
    73 For source code,
    75  git clone
     64==== Defining variables
     65If you just define a plain vanilla variable, then there's not gonna be
     66any dispatching there:
     68 (define horses 23)
     70Any older stuff you had stored in {{horses}} isn't lost, it'll be ready
     71for when you define a new generic using their same car signature.
     73The same goes for
     75 (define horses (lambda () 23))
     77That's not gonna be part of a generic.
     79"Single variables" like this aren't saved, and can be overwritten on
     80next call to {{define}}.
     83==== Resetting & hacking
     84"OMG, I sent a mistyped definition to the REPL and now I can't define
     85over it since this new define just adds stuff!"
     87This doesn't work:
     89 (define (wrong tyop) typo)
     90 (define wrong #f)
     91 (define (wrong typo) typo)
     93It'll have both the {{tyop}} and the {{typo}} version in there.
     95I mean, that'd kind of be fine in this particular case since the
     96dispatcher will see the latest version first and that happens to
     97shadow the typos, but, if you put typos in predicates or whatever you
     98might be in for a rough time.
     100However, to reset when you mistype something and wanna re-define at
     101the REPL, do {{(define reset: <the-name>)}}, so in this example
     103 (define reset: wrong)
     105And to get access to the entire underlying call-table* for your own
     106metamagic and hackery purps just use {{(define)}}.
     108We wanna make easy things easy but any thing possible.♥
     110Emacs nerds, here is an elisp function that resets a define you're in:
     112 (defun mdg-reset-define ()
     113   (interactive)
     114   (save-excursion
     115     (re-search-backward "^(define ")
     116    (forward-char 1)
     117    (re-search-forward "(")
     118    (let* ((end (1- (re-search-forward "[ )]")))
     119       (beg (1+ (re-search-backward "(")))
     120       (str (buffer-substring beg end)))
     121      (comint-send-string
     122       (scheme-proc)
     123       (format "(define reset: %s)\n" str))
     124      (message "Resetting %s" str))))
     126Bind it to whatever. You then need to re-send the sexps you do want.
     129==== The vagaries of DSSSL
     130You can't combine DSSSL (i.e. {{#!key}}, {{#!optional}} and {{#!rest}}) and
     131matchable in the same line, but, as you saw in the {{foop}} example
     132above, the same generic can handle both DSSSL stuff and matchable just
     133fine, in separate clauses.
     135The dispatcher sees all of that stuff as just one big {{. rest}} tail so
     136it catches and collides with a lot of patterns. So be careful if you
     137wanna combine DSSSL and generics.
     140=== Source code
     141This define is available under the name {{define-dx}} in the
     142[[/match-generics|match-generics]] egg.
     144 git source
     146Inside of the [[/brev|brev]] repo, and provided with the big {{brev}} egg, there
     147is an extension named {{mdg}} that imports it under the name {{define}}
     148and the OG define as {{define-og}}. So just {{(import brev mdg)}}, which is
     149now part of the little {{brev}} compiler shell wrapper and is prompted
     150for by the {{brev2scm}} conversion process. Or if you want just the define
     151and not the rest of the brev batteries,
     153 (import (rename match-generics (define-dx define)))
Note: See TracChangeset for help on using the changeset viewer.