Racket Generic Binding Forms
(require generic-bind) | package: generic-bind |
This library implements new Racket binding forms (ie, ~define, ~lambda, ~let, etc.) that support generic binding instances. A generic binding instance is a syntax object that implements an "interface" (currently just some syntax properties) that the binding forms then use to determine how to proceed with the binding.
This moves the binding "logic" to the binding site itself and enables one binding form to support many different binding modes (ie, match or values), rather than have to manually implement a version of each binding form for all the different combinations (ie, define-values, match-define, match-define-values, etc.).
The hope is that the forms in this library can be used in place of their analogous Racket counterparts.
1 Generic Binding Instances
The forms described in this section may be used only in binding positions of binding forms that support generic bindings. Any other use is invalid. For examples, see the next section, Core Generic Binding Forms.
bindings that may be used anywhere, and
bindings that may only be used in "define" contexts.
The second category is a subset of the first and is needed in order to handle Racket’s multiple return values. Since Racket functions cannot receive values, we must know what we are binding at the time of the binding. For example, this means that ~let may support values binding, but ~define or ~lambda may not. Thus, the second category essentially the first, but without values-bindings.
A few generic binding instances are currently supported. Defining new generic bindings is currently limited to new match-specific binding instances. See Implementing New Generic Binding Instances for more information.
syntax
($ match-pattern)
Other generic binding instances can be nested within the match pattern.
syntax
(~vs b ...)
b = define-allowable-generic-binding
Currently supports one-level of nested generic binding instances. In other words, each binding site in the ~vs form may be either an identifier, or another generic binding instance. However, any nested binding positions are considered "define" contexts. This means that one cannot nest ~vs bindings.
syntax
(⋈ b ...)
syntax
($: x xs)
syntax
($stx pattern pattern-directive ...)
syntax
($and b ...)
b = generic-binding
syntax
($c db contract-expr)
($c b (values contract-expr ...))
db = define-allowable-generic-binding b = generic-binding
2 Core Generic Binding Forms
syntax
(~define b body)
(~define (f db ...) body ...)
b = generic-binding | identifier? db = define-allowable-generic-binding | identifier?
When using ~define to define a function, any generic binding must be define-allowable. (So the ~vs values-binding form is not allowed.)
> (~define ($ (list y1 y2)) (list 10 20))
> y1 10
> y2 20
> (~define ($ (list (cons y3 y4) y5)) (list (cons 20 30) 40))
> y3 20
> y4 30
> y5 40
> (~define (⋈ v1 v2) (values 101 202))
> v1 101
> v2 202
> (~define (⋈ ($ (list x4 x5)) x6) (values (list 99 999) 9999))
> x4 99
> x5 999
> x6 9999
> (struct A (x y))
> (~define ($ (A x7 y7)) (A 101 202))
> x7 101
> y7 202
> (~define ($stx ((~seq kw:keyword arg:expr) ...)) #'(#:a 1 #:b 2 #:c 3))
> #'(kw ...) #<syntax:25:0 (#:a #:b #:c)>
> #'(arg ...) #<syntax:26:0 (1 2 3)>
> #'([kw . arg] ...) #<syntax:27:0 ((#:a . 1) (#:b . 2) (#:c . 3))>
> (~define (f1 x [y 10] #:z [z 0]) (+ x y z))
> (f1 100) 110
> (f1 100 200) 300
> (f1 100 200 #:z 300) 600
> (~define (f3 . rst) rst)
> (f3 1 2 3) '(1 2 3)
> (f3) '()
> (~define (f4 x y . rst) (cons x (cons y rst)))
> (f4 1 2 3 4 5 6) '(1 2 3 4 5 6)
> (f4 1 2) '(1 2)
> (~define (f2 ($ (list x y))) (- x y))
> (f2 (list 145 45)) 100
> (~define (g1 ($ (list (list a b) y ...))) (apply + a b y))
> (g1 (list (list 101 202) 303)) 606
> (~define (f5 ($ (list (list x y) z)) . rst) (cons x (cons y (cons z rst))))
> (f5 (list (list 1 2) 3)) '(1 2 3)
> (f5 (list (list 1 2) 3) 4 5) '(1 2 3 4 5)
> (~define (gg ($ xxx)) (add1 xxx))
> (gg 10001) 10002
> (~define (ggg ($ _)) 12345)
> (ggg 5432) 12345
> (~define (gggg ($ 11111)) 22222)
> (gggg 111) match-define: no matching clause for 111
> (gggg 11111) 22222
> (~define (f20 ($list x y z : xs)) (+ x y z (length xs)))
> (f20 (list 10 20 30 40 50 60 70 80 90)) 66
> (~define (f21 ($: x xs)) (+ x (length xs)))
> (f21 (list 10 20 30 40 50 60 70 80 90)) 18
> (~define (fkw1 [($list x y) (list 1 2)]) (+ x y 10))
> (fkw1) 13
> (fkw1 (list 10 20)) 40
> (fkw1 10) match-define: no matching clause for 10
> (~define (fkw2 #:A ($list x y)) (+ x y 10))
> (fkw2 #:A (list 1 2)) 13
> (~define (fkw3 #:B [($list x y) (list 1 2)]) (+ x y 10))
> (fkw3 #:B (list 10 20)) 40
> (fkw3 #:B 10) match-define: no matching clause for 10
syntax
(~def ...)
syntax
(~d ...)
syntax
(~define/contract b contract-expr body)
(~define/contract (f db ...) contract-expr body ...)
b = generic-binding | identifier? db = define-allowable-generic-binding | identifier?
syntax
(~lambda db body ...)
(~lambda (db ...) body ...)
db = define-allowable-generic-binding | identifier?
If a single identifier is given with no parentheses, then the arguments are put in a list, just like lambda. A single generic bind instance may also be used without parens. If a list of bindings if given, then each may be either an identifier or a generic bind instance.
syntax
(~lam ...)
syntax
(~l ...)
syntax
(~λ ...)
> ((~λ (x) x) 111) 111
> ((~λ rst rst) 1 2 3) '(1 2 3)
> ((~λ (x [y 0] #:z z #:a [a 10]) (+ x y z a)) 1 #:z 10) 21
> ((~λ (x [y 0] #:z z #:a [a 10]) (+ x y z a)) 1 22 #:z 10) 43
> ((~λ (x [y 0] #:z z #:a [a 10]) (+ x y z a)) 1 #:z 10 #:a 111) 122
> ((~lambda (x [y 0] #:z z #:a [a 10]) (+ x y z a)) 1 #:z 10 #:a 111) 122
> ((~lambda ($: x xs) (append xs (list x))) (list 1 2 3 4 5)) '(2 3 4 5 1)
> ((~lambda (f ($: x xs)) (cons (f x) xs)) add1 (list 1 2 3 4)) '(2 2 3 4)
> ((~lambda (f ($list)) (f 1)) add1 null) 2
> ((~lambda (f ($list)) (f 1)) add1 (list 1 2 3)) match-define: no matching clause for '(1 2 3)
> ((~λ (($ (list x y)) ($ (cons a b))) (+ a b x y)) (list 1 2) (cons 3 4)) 10
> ((~λ (x y ($ (A a b))) (+ x y a b)) 10 20 (A 30 40)) 100
> (~λ ((~vs v1 v2)) (+ v1 v2)) eval:79.0: ~λ: expected a generic bind instance
at: ((~vs v1 v2))
in: (~λ ((~vs v1 v2)) (+ v1 v2))
parsing context:
while parsing a generic bind instance for define contexts
term: ((~vs v1 v2))
location: eval:79.0
> ((~λ ([($: x xs) (list 1 2)]) (+ x (length xs) 20))) 22
> ((~λ ([($: x xs) (list 1 2)]) (+ x (length xs) 20)) (list 1 2 3 4)) 24
> ((~λ (#:C ($: x xs)) (append xs (list x))) #:C (list 1 2 3)) '(2 3 1)
> ((~λ (#:D [($: x xs) (list 10 20 30)]) (append xs (list x)))) '(20 30 10)
> ((~λ (#:D [($: x xs) (list 10 20 30)]) (append xs (list x))) #:D (list 1 2 3)) '(2 3 1)
syntax
(~case-lambda clause ...)
clause = (header body ...)
syntax
(~case-lam clause ...)
syntax
(~case-define f clause1 ...)
(~case-define f clause2 ...)
clause1 = (header body ...) clause2 = (b ... → body ...)
> (~case-define new-map1 [(f $null) null] [(f ($: x xs)) (cons (f x) (new-map1 f xs))])
> (new-map1 add1 null) '()
> (new-map1 add1 (list 1)) '(2)
> (new-map1 add1 (list 1 2)) '(2 3)
> (~case-define new-filter1 [(p? $null) null] [(p? ($: x xs)) (if (p? x) (cons x (new-filter1 p? xs)) (new-filter1 p? xs))])
> (new-filter1 even? null) '()
> (new-filter1 even? (list 1)) '()
> (new-filter1 even? (list 1 2 3 4 5)) '(2 4)
> (~case-define new-foldl1 [(f base $null) base] [(f base ($: x xs)) (new-foldl1 f (f x base) xs)])
> (new-foldl1 - 0 null) 0
> (new-foldl1 - 0 (list 1)) 1
> (foldl - 0 (list 1)) 1
> (new-foldl1 - 0 (list 1 2)) 1
> (foldl - 0 (list 1 2)) 1
> (~case-define new-foldr1 [(f base $null) base] [(f base ($: x xs)) (f x (new-foldr1 f base xs))])
> (new-foldr1 - 0 null) 0
> (new-foldr1 - 0 (list 1)) 1
> (foldr - 0 (list 1)) 1
> (new-foldr1 - 0 (list 1 2)) -1
> (foldr - 0 (list 1 2)) -1
syntax
(~case-def clause ...)
> (~case-def new-map [f $null → null] [f ($: x xs) → (cons (f x) (new-map f xs))])
> (new-map add1 null) '()
> (new-map add1 (list 1)) '(2)
> (new-map add1 (list 1 2)) '(2 3)
> (~case-def new-filter [p? $null → null] [p? ($: (? p? x) xs) → (cons x (new-filter p? xs))] [p? ($: x xs) → (new-filter p? xs)])
> (new-filter even? null) '()
> (new-filter even? (list 1)) '()
> (new-filter even? (list 1 2 3 4 5)) '(2 4)
> (~case-def new-foldl [f base $null → base] [f base ($: x xs) → (new-foldl f (f x base) xs)])
> (new-foldl - 0 null) 0
> (new-foldl - 0 (list 1)) 1
> (foldl - 0 (list 1)) 1
> (new-foldl - 0 (list 1 2)) 1
> (foldl - 0 (list 1 2)) 1
> (~case-def new-foldr [f base $null → base] [f base ($: x xs) → (f x (new-foldr f base xs))])
> (new-foldr - 0 null) 0
> (new-foldr - 0 (list 1)) 1
> (foldr - 0 (list 1)) 1
> (new-foldr - 0 (list 1 2)) -1
> (foldr - 0 (list 1 2)) -1
syntax
(~let loop ([db e] ...) body ...)
(~let ([b e] ...) body ...)
db = define-allowable-generic-bind | identifier? b = generic-bind | identifier?
Note that when using the match $ binding with ~let, the behavior is NOT like match-let, in that there is no coupling between between the binding positions. This means that trying to bind duplicate identifiers in a $ match pattern will produce the same results or errors as in let, let*, or letrec.
> (define x55 155)
> (~let ([x55 (add1 x55)]) x55) 156
> (define x56 156)
> (~let ([x56 157] [x57 x56]) x57) 156
> (~let ([($ (list x58 x59)) (list 158 159)]) (list x59 x58)) '(159 158)
> (~let ([(~vs v17 v27) (values 17 27)]) (+ v17 v27)) 44
> (~let ([(~vs ($ (list x18 y18)) v18) (values (list 18 19) 20)]) (+ x18 y18 v18)) 57
> (~let ([x 1] [x 2]) x) eval:137:0: internal definition: duplicate binding name
at: x
in: (define-values (x) (values g872))
> (~let ([($ (list x y)) (list 1 2)] [x 3]) x) eval:138:0: internal definition: duplicate binding name
at: x
in: (define-values (x) (values g874))
> (~let ([x 3] [($ (list x y)) (list 1 2)]) x) eval:139:0: internal definition: duplicate binding name
at: x
in: (define-values (x y) (letrec-syntaxes
(((current-match-pat-def-set1) (syntax-local-value
(quote-syntax current-match-pat-def-set1878))))
(match*/derived ((values g877)) (match-define (list x y)
(values g877)) (((list x y)) (values x y)))))
> (~let ([($ (list x y)) (list 1 2)] [($ (list x y)) (list 3 4)]) (list x y)) eval:140:0: internal definition: duplicate binding name
at: x
in: (define-values (x y) (letrec-syntaxes
(((current-match-pat-def-set1) (syntax-local-value
(quote-syntax current-match-pat-def-set1882))))
(match*/derived ((values g880)) (match-define (list x y)
(values g880)) (((list x y)) (values x y)))))
> (~let ([($ (list x y)) (list 1 2)] [(~vs x y) (list 3 4)]) (list x y)) eval:141:0: internal definition: duplicate binding name
at: x
in: (define-values (x) g887)
> (~let ([($ (list x x)) (list 2 2)]) x) 2
> (let ([x 12345]) (~let ([x 40] [y x]) y)) 12345
> (let ([x 12345]) (~let ([x 40] [y 1] [z x]) z)) 12345
> (let ([x 12345] [y 5432]) (~let ([x 40] [y 23] [z y]) z)) 5432
> (~let ([x59 x59]) x59) x59: undefined;
cannot reference undefined identifier
> (~let ([f (λ (x) (if (zero? x) 1 (* x (f (sub1 x)))))]) (f 10)) f: undefined;
cannot reference undefined identifier
> (~let L ([($ (list x y ...)) (list 1 2 3 4 5)] [n 4]) (if (zero? n) 0 (+ n x (L y (sub1 n))))) 20
> (let loop ([x 1] [x 2]) x) eval:149:0: let: duplicate identifier
at: x
in: (let loop ((x 1) (x 2)) x)
> (~let loop ([x 1] [x 2]) x) eval:150:0: lambda: duplicate argument name
at: x
in: (lambda (x x) x)
> (define x8 109)
> (let loop ([x8 1] [y x8]) y) 109
> (~let loop ([x8 1] [y x8]) y) 109
syntax
(~let* ([b e] ...) body ...)
b = generic-bind | identifier?
> (~let* ([x 46436] [y x]) y) 46436
> (~let* ([($ (list x y)) (list 1010 2020)] [z (+ x y)]) z) 3030
> (~let* ([(⋈ v1 v2) (values 4040 5050)] [z (+ v1 v2)]) z) 9090
> (~let* ([(⋈ v3 ($ (list xy yx))) (values 202 (list 303 404))] [z (+ v3 (- yx xy))]) z) 303
> (~let* ([x 1122] [(⋈ ($ (cons ab bc)) ($ (list-rest y ys))) (values (cons x (* x 2)) (list x (add1 x) (sub1 x)))]) (+ (- bc ab) (- y (+ (car ys) (cadr ys))))) 0
> (~let* ([x 101] [x 202]) x) 202
syntax
(~letrec ([b e] ...) body ...)
b = generic-bind | identifier?
> (require math/number-theory)
> (~letrec ([x x]) x) x: undefined;
cannot use before initialization
> (~letrec ([f (λ (x) (if (zero? x) 1 (* x (f (sub1 x)))))]) (f 10)) 3628800
> (factorial 10) 3628800
> (~letrec ([evn? (λ (x) (if (zero? x) #t (od? (sub1 x))))] [od? (λ (x) (if (zero? x) #f (evn? (sub1 x))))]) (evn? 10)) #t
> (~letrec ([(⋈ evn? od?) (values (λ (x) (if (zero? x) #t (od? (sub1 x)))) (λ (x) (if (zero? x) #f (evn? (sub1 x)))))]) (evn? 10)) #t
> (~letrec ([($ (list evn? od?)) (list (λ (x) (if (zero? x) #t (od? (sub1 x)))) (λ (x) (if (zero? x) #f (evn? (sub1 x)))))]) (and (od? 101) (evn? 10))) #t
> (~letrec ([(⋈ evn? ($ (list od?))) (values (λ (x) (if (zero? x) #t (od? (sub1 x)))) (list (λ (x) (if (zero? x) #f (evn? (sub1 x))))))]) (and (od? 101) (evn? 10))) #t
3 Generic Comprehension Forms
All the forms in this section are the same as their Racket counterparts (see for), but with added generic bind support.
syntax
(~for ...)
syntax
(~for/list ...)
syntax
(~for/lists ...)
syntax
(~for/vector ...)
syntax
(~for/fold ...)
syntax
(~for/first ...)
syntax
(~for/last ...)
syntax
(~for/or ...)
syntax
(~for/and ...)
syntax
(~for/sum ...)
syntax
(~for/product ...)
syntax
(~for/hash ...)
syntax
(~for/hasheq ...)
syntax
(~for/hasheqv ...)
syntax
(~for* ...)
syntax
(~for*/list ...)
syntax
(~for*/lists ...)
syntax
(~for*/vector ...)
syntax
(~for*/fold ...)
syntax
(~for*/first ...)
syntax
(~for*/last ...)
syntax
(~for*/or ...)
syntax
(~for*/and ...)
syntax
(~for*/sum ...)
syntax
(~for*/product ...)
syntax
(~for*/hash ...)
syntax
(~for*/hasheq ...)
syntax
(~for*/hasheqv ...)
> (~for/list ([($ (list x y)) '((1 2) (3 4) (5 6))]) (list y x)) '((2 1) (4 3) (6 5))
> (~for/list ([($ (list x y)) '((1 2) (3 4))] [($ (list a b)) '((5 6) (7 8))]) (list y x b a)) '((2 1 6 5) (4 3 8 7))
> (~for/list ([($ (list x y)) '((1 2) (3 4))] [($ (list a b)) '((5 6) (7 8))] #:when (= x 1)) (list y x b a)) '((2 1 6 5))
> (~for/list ([($ (list x y)) '((1 2) (3 4))] [($ (list a b)) '((5 6) (7 8))] #:unless (= x 1)) (list y x b a)) '((4 3 8 7))
> (~for/list ([($ (list x y)) '((2 2) (1 4) (1 4))] [($ (list a b)) '((5 6) (6 7) (7 8))] #:when (= x 1) #:unless (= a 6)) (list y x b a)) '((4 1 8 7))
> (~for/list ([x '(1 2 3)] #:break (= x 2)) x) '(1)
> (~for/list ([x '(1 2 3)] #:final (= x 2)) x) '(1 2)
> (~for*/list ([x '(1 2 3)]) #:break (= x 2) x) '(1)
> (~for*/list ([x '(1 2 3)]) #:final (= x 2) x) '(1 2)
> (~for/list ([x '(1 2 3)] #:when #t [y '(4 5 6)] #:when #t) (list x y)) '((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6))
> (~for*/list ([x '(1 2 3)] [y '(4 5 6)]) (list x y)) '((1 4) (1 5) (1 6) (2 4) (2 5) (2 6) (3 4) (3 5) (3 6))
> (require racket/generator)
> (~for/list ([($ (list x y)) (in-generator (let loop ([n 3]) (unless (zero? n) (yield (list n (add1 n))) (loop (sub1 n)))))]) (list x y)) '((3 4) (2 3) (1 2))
> (~for/fold ([sum 0]) ([x '(1 2 3 4 5 6)]) (+ x sum)) 21
> (for/fold ([sum 0]) ([x '(1 2 3 4 5 6)]) (+ x sum)) 21
> (~for/and ([x (list #t #f #t)]) (displayln x) x)
#t
#f
#f
> (~for*/list ([($ (list x y)) (list (list (list 1 2 3) (list 4 5 6)) (list (list 10 11 12) (list 13 14 15)))] [a x] [b y]) (list a b))
'((1 4)
(1 5)
(1 6)
(2 4)
(2 5)
(2 6)
(3 4)
(3 5)
(3 6)
(10 13)
(10 14)
(10 15)
(11 13)
(11 14)
(11 15)
(12 13)
(12 14)
(12 15))
> (for*/list ([lst (list (list (list 1 2 3) (list 4 5 6)) (list (list 10 11 12) (list 13 14 15)))] [a (car lst)] [b (cadr lst)]) (list a b))
'((1 4)
(1 5)
(1 6)
(2 4)
(2 5)
(2 6)
(3 4)
(3 5)
(3 6)
(10 13)
(10 14)
(10 15)
(11 13)
(11 14)
(11 15)
(12 13)
(12 14)
(12 15))
> (~for*/list ([(~vs v1 v2) (in-hash (hash 1 2 3 4))] [x v1] [y v2]) (cons x y))
'((0 . 0)
(0 . 1)
(0 . 0)
(0 . 1)
(0 . 2)
(0 . 3)
(1 . 0)
(1 . 1)
(1 . 2)
(1 . 3)
(2 . 0)
(2 . 1)
(2 . 2)
(2 . 3))
> (for*/list ([(v1 v2) (in-hash (hash 1 2 3 4))] [x v1] [y v2]) (cons x y))
'((0 . 0)
(0 . 1)
(0 . 0)
(0 . 1)
(0 . 2)
(0 . 3)
(1 . 0)
(1 . 1)
(1 . 2)
(1 . 3)
(2 . 0)
(2 . 1)
(2 . 2)
(2 . 3))
> (~for*/list ([(v1 v2) (in-hash (hash 1 2 3 4))] [x v1] [y v2]) (cons x y))
'((0 . 0)
(0 . 1)
(0 . 0)
(0 . 1)
(0 . 2)
(0 . 3)
(1 . 0)
(1 . 1)
(1 . 2)
(1 . 3)
(2 . 0)
(2 . 1)
(2 . 2)
(2 . 3))
> (~for*/list ([(~vs ($ (list x y)) ($ (list a b))) (in-hash (hash (list 1 2) (list 3 4) (list 5 6) (list 7 8)))] [c x]) (list x y a b c)) '((1 2 3 4 0) (5 6 7 8 0) (5 6 7 8 1) (5 6 7 8 2) (5 6 7 8 3) (5 6 7 8 4))
> (for*/list ([(lst1 lst2) (in-hash (hash (list 1 2) (list 3 4) (list 5 6) (list 7 8)))] [c (car lst1)]) (list (car lst1) (cadr lst1) (car lst2) (cadr lst2) c)) '((1 2 3 4 0) (5 6 7 8 0) (5 6 7 8 1) (5 6 7 8 2) (5 6 7 8 3) (5 6 7 8 4))
4 Implementing New Generic Binding Instances
Only defining match-specific new binding instances are currently possible.
syntax
(define-match-bind (name x ...))
(define-match-bind name)
> (struct B (x y z))
> (define-match-bind (B x y z))
> (~define (bf ($B x y z)) (+ x y z))
> (bf (B 20 40 60)) 120
> (define-match-bind hash-table)
> (~define ($hash-table [keys vals] ...) (hash 'a 1 'b 2 'c 3))
> keys '(c b a)
> vals '(3 2 1)
syntax
(~struct ...)
> (~struct C (a b c [d #:mutable]))
> (define c (C 9 8 7 6))
> (C? c) #t
> (C-a c) 9
> (C-b c) 8
> (C-c c) 7
> (C-d c) 6
> (set-C-d! c 20)
> (C-d c) 20