From 003ac9b33880b032bcae8d8e28f5cf737c1dac94 Mon Sep 17 00:00:00 2001 From: Alex Knauth Date: Wed, 7 Oct 2020 01:11:02 -0400 Subject: [PATCH] add keyword-apply/dict to racket/dict (#2592) * add keyword-apply/dict to racket/dict * add history note --- .../scribblings/reference/dicts.scrbl | 51 +++++++++- .../tests/racket/keyword-apply-dict.rkt | 98 +++++++++++++++++++ racket/collects/racket/dict.rkt | 3 + .../racket/private/keyword-apply-dict.rkt | 61 ++++++++++++ 4 files changed, 212 insertions(+), 1 deletion(-) create mode 100644 pkgs/racket-test/tests/racket/keyword-apply-dict.rkt create mode 100644 racket/collects/racket/private/keyword-apply-dict.rkt diff --git a/pkgs/racket-doc/scribblings/reference/dicts.scrbl b/pkgs/racket-doc/scribblings/reference/dicts.scrbl index f0472b8ab9..eb0b84e1ed 100644 --- a/pkgs/racket-doc/scribblings/reference/dicts.scrbl +++ b/pkgs/racket-doc/scribblings/reference/dicts.scrbl @@ -2,7 +2,8 @@ @(require "mz.rkt" (for-label racket/generic)) @(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} @@ -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-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-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[#:] keywords must not overlap. +The given @racket[proc] must accept all of the keywords in +@racket[kw-dict] plus the @racket[#:]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] diff --git a/pkgs/racket-test/tests/racket/keyword-apply-dict.rkt b/pkgs/racket-test/tests/racket/keyword-apply-dict.rkt new file mode 100644 index 0000000000..23e29d837f --- /dev/null +++ b/pkgs/racket-test/tests/racket/keyword-apply-dict.rkt @@ -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 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 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))) +