add keyword-apply/dict to racket/dict (#2592)
* add keyword-apply/dict to racket/dict * add history note
This commit is contained in:
parent
c471e3192b
commit
003ac9b338
|
@ -2,7 +2,8 @@
|
||||||
@(require "mz.rkt" (for-label racket/generic))
|
@(require "mz.rkt" (for-label racket/generic))
|
||||||
|
|
||||||
@(define dict-eval (make-base-eval))
|
@(define dict-eval (make-base-eval))
|
||||||
@examples[#:hidden #:eval dict-eval (require racket/dict racket/generic racket/contract)]
|
@examples[#:hidden #:eval dict-eval
|
||||||
|
(require racket/dict racket/generic racket/contract racket/string)]
|
||||||
|
|
||||||
@title[#:tag "dicts"]{Dictionaries}
|
@title[#:tag "dicts"]{Dictionaries}
|
||||||
|
|
||||||
|
@ -1014,4 +1015,52 @@ See also @racket[define-custom-hash-types].
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@section{Passing keyword arguments in dictionaries}
|
||||||
|
|
||||||
|
@defproc[
|
||||||
|
(keyword-apply/dict [proc procedure?]
|
||||||
|
[kw-dict dict?] ; (dict/c keyword? any/c)
|
||||||
|
[pos-arg any/c] ...
|
||||||
|
[pos-args (listof any/c)]
|
||||||
|
[#:<kw> kw-arg any/c] ...)
|
||||||
|
any]{
|
||||||
|
Applies the @racket[proc] using the positional arguments
|
||||||
|
from @racket[(list* pos-arg ... pos-args)], and the keyword
|
||||||
|
arguments from @racket[kw-dict] in addition to the directly
|
||||||
|
supplied keyword arguments in the @racket[#:<kw> kw-arg]
|
||||||
|
sequence.
|
||||||
|
|
||||||
|
All the keys in @racket[kw-dict] must be keywords.
|
||||||
|
The keywords in the @racket[kw-dict] do not have to be
|
||||||
|
sorted. However, the keywords in @racket[kw-dict] and the
|
||||||
|
directly supplied @racket[#:<kw>] keywords must not overlap.
|
||||||
|
The given @racket[proc] must accept all of the keywords in
|
||||||
|
@racket[kw-dict] plus the @racket[#:<kw>]s.
|
||||||
|
|
||||||
|
@examples[
|
||||||
|
#:eval dict-eval
|
||||||
|
(define (sundae #:ice-cream [ice-cream '("vanilla")]
|
||||||
|
#:toppings [toppings '("brownie-bits")]
|
||||||
|
#:sprinkles [sprinkles "chocolate"]
|
||||||
|
#:syrup [syrup "caramel"])
|
||||||
|
(format "A sundae with ~a ice cream, ~a, ~a sprinkles, and ~a syrup."
|
||||||
|
(string-join ice-cream #:before-last " and ")
|
||||||
|
(string-join toppings #:before-last " and ")
|
||||||
|
sprinkles
|
||||||
|
syrup))
|
||||||
|
(keyword-apply/dict sundae '((#:ice-cream . ("chocolate"))) '())
|
||||||
|
(keyword-apply/dict sundae
|
||||||
|
(hash '#:toppings '("cookie-dough")
|
||||||
|
'#:sprinkles "rainbow"
|
||||||
|
'#:syrup "chocolate")
|
||||||
|
'())
|
||||||
|
(keyword-apply/dict sundae
|
||||||
|
#:sprinkles "rainbow"
|
||||||
|
(hash '#:toppings '("cookie-dough")
|
||||||
|
'#:syrup "chocolate")
|
||||||
|
'())
|
||||||
|
]
|
||||||
|
@history[#:added "7.9"]}
|
||||||
|
|
||||||
|
|
||||||
@close-eval[dict-eval]
|
@close-eval[dict-eval]
|
||||||
|
|
98
pkgs/racket-test/tests/racket/keyword-apply-dict.rkt
Normal file
98
pkgs/racket-test/tests/racket/keyword-apply-dict.rkt
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
#lang racket/base
|
||||||
|
(require rackunit racket/dict racket/list racket/string racket/math)
|
||||||
|
|
||||||
|
(define (sorted-assoc-dict alst) (sort alst keyword<? #:key car))
|
||||||
|
(define (unsorted-assoc-dict alst)
|
||||||
|
(cond [(or (empty? alst) (empty? (rest alst))) alst]
|
||||||
|
[else
|
||||||
|
(define shf (shuffle alst))
|
||||||
|
(if (apply keyword<? (map car shf)) (reverse shf) shf)]))
|
||||||
|
(define assoc-dicts (list sorted-assoc-dict unsorted-assoc-dict))
|
||||||
|
|
||||||
|
(define ihash-dict make-immutable-hash)
|
||||||
|
(define ihasheqv-dict make-immutable-hasheqv)
|
||||||
|
(define ihasheq-dict make-immutable-hasheq)
|
||||||
|
(define ihash-dicts (list ihash-dict ihasheqv-dict ihasheq-dict))
|
||||||
|
|
||||||
|
(define mhash-dict make-hash)
|
||||||
|
(define mhasheqv-dict make-hasheqv)
|
||||||
|
(define mhasheq-dict make-hasheq)
|
||||||
|
(define mhash-dicts (list mhash-dict mhasheqv-dict mhasheq-dict))
|
||||||
|
|
||||||
|
(define dicts (append assoc-dicts ihash-dicts mhash-dicts))
|
||||||
|
|
||||||
|
(for ([d (in-list dicts)])
|
||||||
|
(define (name x)
|
||||||
|
(format "keyword-apply/dict with ~a: ~a" (object-name d) x))
|
||||||
|
|
||||||
|
(test-case (name "go")
|
||||||
|
(define (go #:mode mode target) (list target mode))
|
||||||
|
(check-equal? (keyword-apply/dict go (d '((#:mode . fast))) '("super.rkt"))
|
||||||
|
'("super.rkt" fast)))
|
||||||
|
|
||||||
|
(test-case (name "sundae")
|
||||||
|
(define (sundae #:ice-cream [ice-cream '("vanilla")]
|
||||||
|
#:toppings [toppings '("brownie-bits")]
|
||||||
|
#:sprinkles [sprinkles "chocolate"]
|
||||||
|
#:syrup [syrup "caramel"])
|
||||||
|
(format "A sundae with ~a ice cream, ~a, ~a sprinkles, and ~a syrup."
|
||||||
|
(string-join ice-cream #:before-last " and ")
|
||||||
|
(string-join toppings #:before-last " and ")
|
||||||
|
sprinkles
|
||||||
|
syrup))
|
||||||
|
(check-equal? (keyword-apply/dict sundae
|
||||||
|
(d '((#:ice-cream . ("chocolate"))))
|
||||||
|
'())
|
||||||
|
"A sundae with chocolate ice cream, brownie-bits, chocolate sprinkles, and caramel syrup.")
|
||||||
|
(check-equal? (keyword-apply/dict sundae
|
||||||
|
(d '((#:toppings . ("cookie-dough"))
|
||||||
|
(#:sprinkles . "rainbow")
|
||||||
|
(#:syrup . "chocolate")))
|
||||||
|
'())
|
||||||
|
"A sundae with vanilla ice cream, cookie-dough, rainbow sprinkles, and chocolate syrup.")
|
||||||
|
(check-equal? (keyword-apply/dict sundae
|
||||||
|
#:sprinkles "rainbow"
|
||||||
|
(d '((#:toppings . ("cookie-dough"))
|
||||||
|
(#:syrup . "chocolate")))
|
||||||
|
'())
|
||||||
|
"A sundae with vanilla ice cream, cookie-dough, rainbow sprinkles, and chocolate syrup."))
|
||||||
|
|
||||||
|
(test-case (name "f mand y opt z")
|
||||||
|
(define (f x #:y y #:z [z 10])
|
||||||
|
(list x y z))
|
||||||
|
|
||||||
|
(check-equal? (keyword-apply/dict f (d '((#:y . 2))) '(1)) '(1 2 10))
|
||||||
|
(check-equal? (keyword-apply/dict f (d '((#:y . 2) (#:z . 3))) '(1)) '(1 2 3))
|
||||||
|
(check-equal? (keyword-apply/dict f #:z 7 (d '((#:y . 2))) '(1)) '(1 2 7)))
|
||||||
|
|
||||||
|
(test-case (name "dotted-h")
|
||||||
|
(define (dotted-h x #:y [y 12])
|
||||||
|
(list x y))
|
||||||
|
|
||||||
|
(check-equal? (keyword-apply/dict dotted-h (d '()) (list 2)) '(2 12))
|
||||||
|
(check-equal? (keyword-apply/dict dotted-h (d '((#:y . 8))) (list 3)) '(3 8))
|
||||||
|
(check-equal? (keyword-apply/dict dotted-h (d '((#:y . 14))) '(g)) '(g 14)))
|
||||||
|
|
||||||
|
(test-case (name "f mand a b c")
|
||||||
|
(define (f #:a a #:b b #:c c d e f) (list a b c d e f))
|
||||||
|
|
||||||
|
(check-equal? (keyword-apply/dict
|
||||||
|
f
|
||||||
|
(d '((#:b . "b") (#:c . "c") (#:a . "a")))
|
||||||
|
'("d" "e" "f"))
|
||||||
|
(list "a" "b" "c" "d" "e" "f")))
|
||||||
|
|
||||||
|
(test-case (name "kinetic-energy")
|
||||||
|
(define (kinetic-energy #:mass m #:velocity v)
|
||||||
|
(* 1/2 m (sqr v)))
|
||||||
|
(check-equal? (keyword-apply/dict kinetic-energy '((#:mass . 2) (#:velocity . 1)) '())
|
||||||
|
1)
|
||||||
|
(check-equal? (keyword-apply/dict kinetic-energy '((#:mass . 5) (#:velocity . 3)) '())
|
||||||
|
(+ 22 1/2)))
|
||||||
|
|
||||||
|
(test-case (name "error keyword duplicated")
|
||||||
|
(check-exn
|
||||||
|
#rx"keyword-apply/dict: keyword duplicated in dict and direct keyword arguments: '#:color"
|
||||||
|
(λ ()
|
||||||
|
(keyword-apply/dict void (d '((#:color . "green"))) #:color "red" '())))))
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#lang racket/base
|
#lang racket/base
|
||||||
(require racket/contract/base
|
(require racket/contract/base
|
||||||
"private/dict.rkt"
|
"private/dict.rkt"
|
||||||
|
"private/keyword-apply-dict.rkt"
|
||||||
"private/custom-hash.rkt")
|
"private/custom-hash.rkt")
|
||||||
|
|
||||||
(define (dict-implements/c . syms)
|
(define (dict-implements/c . syms)
|
||||||
|
@ -287,6 +288,8 @@
|
||||||
in-dict-values
|
in-dict-values
|
||||||
in-dict-pairs
|
in-dict-pairs
|
||||||
|
|
||||||
|
keyword-apply/dict
|
||||||
|
|
||||||
dict-key-contract
|
dict-key-contract
|
||||||
dict-value-contract
|
dict-value-contract
|
||||||
dict-iter-contract)
|
dict-iter-contract)
|
||||||
|
|
61
racket/collects/racket/private/keyword-apply-dict.rkt
Normal file
61
racket/collects/racket/private/keyword-apply-dict.rkt
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#lang racket/base
|
||||||
|
|
||||||
|
(provide keyword-apply/dict)
|
||||||
|
|
||||||
|
(require "dict.rkt")
|
||||||
|
|
||||||
|
;; Proc [Dictof Kw Any] Any ... [Listof Any] -> Any
|
||||||
|
(define keyword-apply/dict
|
||||||
|
(let ()
|
||||||
|
;; keys : [Dictof Kw Any] -> [Listof Kw]
|
||||||
|
;; Produces the sorted list of keys
|
||||||
|
(define (keys kws)
|
||||||
|
(unless (dict? kws)
|
||||||
|
(raise-argument-error 'keyword-apply/dict "dict" kws))
|
||||||
|
(define ks (dict-keys kws))
|
||||||
|
(unless (andmap keyword? ks)
|
||||||
|
(raise-argument-error 'keyword-apply/dict
|
||||||
|
"dict with keyword keys"
|
||||||
|
kws))
|
||||||
|
(sort ks keyword<?))
|
||||||
|
|
||||||
|
;; vals : [Dictof Kw Any] [Listof Kw] -> Any
|
||||||
|
;; Produces the list of vals in the same order as ks
|
||||||
|
(define (vals kws ks)
|
||||||
|
(for/list ([k (in-list ks)]) (dict-ref kws k)))
|
||||||
|
|
||||||
|
;; check-dup : [Listof Kw] [Listof Kw] -> Void
|
||||||
|
(define (check-dup ks1 ks2)
|
||||||
|
(for ([k1 (in-list ks1)] #:when (memq k1 ks2))
|
||||||
|
(raise-mismatch-error
|
||||||
|
'keyword-apply/dict
|
||||||
|
"keyword duplicated in dict and direct keyword arguments: "
|
||||||
|
k1)))
|
||||||
|
|
||||||
|
;; Proc [Dictof Kw Any] Any ... [Listof Any] -> Any
|
||||||
|
;; Used when keyword-apply/dict itself isn't used with keyword arguments
|
||||||
|
(define keyword-apply/dict
|
||||||
|
(case-lambda
|
||||||
|
[(f kws args)
|
||||||
|
(define ks (keys kws))
|
||||||
|
(keyword-apply f ks (vals kws ks) args)]
|
||||||
|
[(f kws arg . rst)
|
||||||
|
(define ks (keys kws))
|
||||||
|
(apply keyword-apply f ks (vals kws ks) arg rst)]))
|
||||||
|
|
||||||
|
;; [Listof Kw] [Listof Any] Proc [Dictof Kw Any] Any ... [Listof Any] -> Any
|
||||||
|
;; Used when keyword-apply/dict itself is passed keyword arguments
|
||||||
|
;; Direct keywords are in ks1, dict is kws2
|
||||||
|
(define kw-proc-keyword-apply/dict
|
||||||
|
(case-lambda
|
||||||
|
[(ks1 vs1 f kws2 args)
|
||||||
|
(define ks2 (keys kws2))
|
||||||
|
(check-dup ks1 ks2)
|
||||||
|
(keyword-apply keyword-apply ks1 vs1 f ks2 (vals kws2 ks2) args '())]
|
||||||
|
[(ks1 vs1 f kws2 arg . rst)
|
||||||
|
(define ks2 (keys kws2))
|
||||||
|
(check-dup ks1 ks2)
|
||||||
|
(keyword-apply keyword-apply ks1 vs1 f ks2 (vals kws2 ks2) arg rst)]))
|
||||||
|
|
||||||
|
(make-keyword-procedure kw-proc-keyword-apply/dict keyword-apply/dict)))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user