Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

js-dict -- syntaxes to manipulate dictionary inspired by JavaScript #17

Open
sorawee opened this issue Aug 19, 2021 · 0 comments
Open

Comments

@sorawee
Copy link

sorawee commented Aug 19, 2021

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 to 3;
  • 'b' maps to 3;
  • 'ab' maps to 4; and
  • 'x' maps to 42

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 to 4;
  • 'b' maps to 2;
  • 'c' maps to 3; and
  • 'd' maps to 5

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 equal 40
  • c should be equal 3
  • d should be equal 4
  • x should be equal 10
  • 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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant