-
-
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)
f
should be equal40
c
should be equal3
d
should be equal4
x
should be equal10
rst
should 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