Thanks to visit codestin.com
Credit goes to github.com

Skip to content

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

@sorawee

Description

@sorawee

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions