-
-
Notifications
You must be signed in to change notification settings - Fork 3
Description
Background
JavaScript has really elegant syntaxes to manipulate dictionaries.
Dictionary creation:
Given:
let x = 42;
{a: 1 + 2, b: 3, ['a' + 'b']: 4, x} is a dictionary with four entries:
'a'maps to3;'b'maps to3;'ab'maps to4; and'x'maps to42
Merging
Other dictionaries can also be merged as a part of dictionary creation.
Given:
let a = {a: 1, c: 2};
let b = {b: 2, c: 3};
Then {b: 42, ...a, ...b, a: 4, d: 5} has four entries:
'a'maps to4;'b'maps to2;'c'maps to3; and'd'maps to5
Note that object merging can be used to set a value functionally without mutating the dictionary.
Extraction:
Given:
let x = {a: 1, b: 2, c: 3, d: 4};
let {a, b: bp} = x; binds a to 1 and bp to 2.
Extraction of the rest
As a part of extraction, there can be at most one ..., which will function as the extraction of the rest
For example:
let {a, b: bp, ...y} = x; binds a to 1, bp to 2, y to {c: 3, d: 4}.
js-dict and js-extract macros
The js-dict and js-extract macros bring these operations to Racket, using immutable hash tables as the data structure. Additionally, the js-extract macro improves upon JS by supporting arbitrary match pattern.
js-dict: dictionary creation
(define d 4)
(define base-1 (js-dict [x '((10))] [b 20]))
(define base-2 (js-dict [y 30] [a 40]))
(define obj
(js-dict
[a 1]
#:merge base-1
[b 2]
#:merge base-2
[#:expr (string->symbol "c") 3]
d))
Then obj should be '#hash((a . 40) (b . 2) (c . 3) (d . 4) (x . ((10))) (y . 30))
js-extract: dictionary extraction
With the above obj, in the following code:
(js-extract ([#:expr (string->symbol "a") f]
c
d
[x (list (list x))]
#:rest rst)
obj)
fshould be equal40cshould be equal3dshould be equal4xshould be equal10rstshould be equal'#hash((b . 2) (y . 30))
Macro
#lang racket/base
(require syntax/parse/define
racket/match
racket/hash
racket/splicing
(for-syntax racket/base
racket/list))
(begin-for-syntax
(define-splicing-syntax-class key
(pattern {~seq #:expr key:expr}
#:with static #'())
(pattern {~seq key*:id}
#:with key #''key*
#:with static #'(key*)))
(define-splicing-syntax-class construct-spec
(pattern {~seq [key:key val:expr]}
#:with code #'`[#:set ,key.key ,val]
#:with (static ...) #'key.static)
(pattern {~seq #:merge e:expr}
#:with code #'`[#:merge ,e]
#:with (static ...) #'())
(pattern {~seq x:id}
#:with code #'`[#:set x ,x]
#:with (static ...) #'(x)))
(define-syntax-class extract-spec
(pattern [key*:key pat:expr]
#:with key #'key*.key
#:with (static ...) #'key*.static)
(pattern x:id
#:with key #''x
#:with pat #'x
#:with (static ...) #'(x))))
(define (make-dict . xs)
(for/fold ([h (hash)]) ([x (in-list xs)])
(match x
[`[#:set ,key ,val] (hash-set h key val)]
[`[#:merge ,d] (hash-union h d #:combine (λ (a b) b))])))
(define-syntax-parse-rule (js-dict spec:construct-spec ...)
#:fail-when
(check-duplicate-identifier (append* (attribute spec.static)))
"duplicate static key"
(make-dict spec.code ...))
(define-syntax-parser extract
[(_ () pat-rst rst-obj) #'(match-define pat-rst rst-obj)]
[(_ (spec:extract-spec specs ...) pat-rst rst-obj)
#'(splicing-let ([KEY spec.key]
[OBJ rst-obj])
(match-define spec.pat (hash-ref OBJ KEY))
(extract (specs ...) pat-rst (hash-remove OBJ KEY)))])
(define-syntax-parse-rule (js-extract (spec:extract-spec ...
{~optional {~seq #:rest e:expr}})
obj:expr)
#:fail-when
(check-duplicate-identifier (append* (attribute spec.static)))
"duplicate static key"
(extract (spec ...) (~? e _) obj))
(module+ test
(require rackunit)
(test-begin
(define d 4)
(define base-1 (js-dict [x '((10))] [b 20]))
(define base-2 (js-dict [y 30] [a 40]))
(define obj
(js-dict
[a 1]
#:merge base-1
[b 2]
#:merge base-2
[#:expr (string->symbol "c") 3]
d))
(let ()
(js-extract ([#:expr (string->symbol "a") f]
c
d
[x (list (list x))]
#:rest rst)
obj)
(check-equal? f 40)
(check-equal? c 3)
(check-equal? d 4)
(check-equal? x 10)
(println rst)
(check-equal? rst (js-dict [y 30] [b 2])))))
Licence
MIT / CC-BY 4.0