Hi, Racket Discourse.
I am busy writing an "interface" which allows me to use a macro-defined template to access the content of the equivalents of JSON objects, i.e.,
hasheq tables, returned by
response-json from API endpoint responses.
This is mostly for my own use, so I am just playing around, but nonetheless I came across some interesting things that I'd like to ask about.
The code below demonstrates the usage of the macro, called
define-hash-interface, which itself defines a macro called
with-hash-interface used to access the template defined by the interface.
The macros are invoked using the forms:
(define-hash-interface NAME FETCH: TREE ...+ WHERE: (PNAME means PRED?) ...) ;; and (with-hash-interface (NAME HASH) BODY ...)
FETCH: TREE ...+ specifies the structure of the expected hash-table and the
WHERE: (PNAME means PRED?) ... specifies any extra predicates that you might want to "bundle" with the interface.
#lang racket (require (for-syntax racket/list racket/string racket/match racket/syntax syntax/parse)) (begin-for-syntax (define (walk-key-tree key-tree) (match key-tree [(list (? symbol? c) 'is type?) (list (list c type?))] [(list (? symbol? head) content ..1) (map (lambda (x) (cons head x)) (append* (map walk-key-tree content)))] [_ (error 'walk-key-tree "expected valid key-tree, got ~v." key-tree)])) (define (format-ids lctx symbols) (for/list ([id symbols]) (format-id lctx "~a" id))) (define (dot-keys key-symbols) (string->symbol (string-join (map symbol->string key-symbols) ".")))) (define (get-in h keys) (define (getter key) (lambda (g) (hash-ref g key))) ((apply compose1 (map getter (reverse keys))) h)) (define-syntax (define-hash-interface stx) (syntax-parse stx #:datum-literals (FETCH: WHERE: means) [(define-hash-interface NAME FETCH: TREE ...+ WHERE: (PNAME means PRED?) ...) (with-syntax* ([((key ... pred?) ...) (append* (map walk-key-tree (syntax->datum #'(TREE ...))))] [(dot-id ...) (format-ids stx (map dot-keys (syntax->datum #'((key ...) ...))))] [interface-handle (format-id stx "~a" 'with-hash-interface)]) #'(define-syntax (interface-handle stx) (syntax-parse stx [(interface-handle (NAME HASH) BODY (... ...)) (with-syntax ([(dot-id ...) (format-ids stx (map dot-keys (syntax->datum #'((HASH dot-id) ...))))]) #'(let ([PNAME PRED?] ...) (define (dot-id) (define val (get-in HASH (list 'key ...))) (unless (pred? val) (error 'with-hash-interface "~a expected value for which ~a is true, got ~v." dot-id pred? val)) val) ... BODY (... ...)))])))] [(define-hash-interface NAME FETCH: TREE ...+) #'(define-hash-interface NAME FETCH: TREE ... WHERE:)])) (define-hash-interface Simulation-Run-Result FETCH: [simulation_id is uint64?] [simulation_run_id is uint64?] [template_id is uint64?] [started_at is int64?] [completed_at is int64?] [status is string?] (status_details (prevention [total_threat_count is int64?] [completed_threat_count is int64?]) (detection [total_threat_count is int64?] [completed_threat_count is int64?])) WHERE: (int64? means integer?) (uint64? means integer?)) (define h (hasheq 'simulation_run_id 1 'status "COMPLETE" 'status_details (hasheq 'detection (hasheq 'total_threat_count 12)))) (define (ctxt) (with-hash-interface (Simulation-Run-Result h) (h.status_details.detection.total_threat_count))) (ctxt)
The accessor ids are bound to procedures, so they only attempt to return the value specified by the chained keys, if any exists and assuming it matches the predicate, once they are called.
What caught me off guard, because I only got to a working solution through trial and error, is:
- the usage of
(... ...); I only happened upon this when searching for the error message
syntax: no pattern variables before ellipsis in template, which led me to this Github issue. Why is this necessary?
the fact that I cannot bind the identifiersSolved.
(dot-id ...)in the
let (...)form as I did with
[PNAME PRED?] ...; I ended up using
(define ...)in the body of the
let, because if I do place
[dot-id (lambda () ...)] ...in the arguments of
let, the identifiers for
[PNAME PRED?] ...become unbound. Is this because of the outer and inner uses of
Thanks for reading all that!