Question

I created this:

(define-syntax (with-hash stx)
  (syntax-parse stx
    [(_ obj:id ((~or key:id [new-key:id hash-key:id]) ...) body:expr ...+)
     #'(let ([key (hash-ref obj 'key)] ...
             [new-key (hash-ref obj 'hash-key)] ...)
         (begin body ...))]))

So that I can do this:

  (require rackunit)
  (define h (hash 'id 1 'name "scott"))
  (with-hash h (id [new-name name])
    (check-equal? id 1)
    (check-equal? new-name "scott"))

How can I add an alternative pattern that automatically binds all the hash keys locally without the client specifying them in the call?

ie:

(define h (hash 'id 1 'name "scott"))
(with-hash h
  (check-equal? id 1)
  (check-equal? name "scott"))

I suspect it involves renaming transformers, but am I able to declare syntax parameters and rename them dynamically, based on the runtime hash?

Also, I thought something like this might be on the right track:

(define-syntax (with-hash stx)
  (syntax-parse stx
    [(_ obj:id (key:id ...) body:expr ...+)
     #'(let ([key (hash-ref obj 'key)] ...)
         (begin body ...))]
    [(_ obj:id body:expr ...+)
     #'(with-hash obj (id title) body ...)]))

where I recall the macro and parse out the datums to be bound, but in that case, the id and title variables are not bound, even though the macro works otherwise.

Clearly I'm missing something in my understanding.

Any insights are appreciated.

Thanks.

Was it helpful?

Solution

You can't, really. Variable scoping is a static property, and a hash's keys are a dynamic property, so any solution is going to be wrong. But since you asked, there are two wrong solutions that are vaguely similar to what you're asking for.

One thing you could do is use eval. But when you call eval you will have lost any local variables; see the docs. You can probably work the code out yourself.

Another thing you could do is change the meaning of unbound variable references by shadowing #%top, which is the syntax implicitly wrapped around variable references to unbound (or "bound by the top level environment, maybe") variables. But that means that with-hash will fail to shadow any keys that already have a local or module-level binding. Here's what the code looks like, anyway:

(define-syntax (with-hash stx)
  (syntax-case stx ()
    [(with-hash h . body)
     (with-syntax ([#%top (datum->syntax stx '#%top)])
       #'(let-syntax ([#%top
                       (syntax-rules ()
                         [(#%top . x)
                          (hash-ref h 'x)])])
           (begin . body)))]))

OTHER TIPS

Dang, Ryan responded while I was trying to come up with an answer :) Here is a solution with eval anyways, with the same caveat that others have already expressed.

#lang racket

(require (for-syntax syntax/parse))

(define-syntax (with-hash stx)
  (syntax-parse stx 
   [(_ h:expr body:expr ...+)
    #'(begin
        (define-namespace-anchor a)
        (let ([keys (hash-keys h)])
          (define (mk-bind k) `[,k (hash-ref h (quote ,k))])
          (eval 
           `(let ,(map mk-bind keys) 
              ,@(quote (body ...))) 
           (namespace-anchor->namespace a))))]))

(require rackunit)
(define h (hash 'id 1 'name "scott"))
(with-hash h
  (check-equal? id 1)
  (check-equal? name "scott"))

EDIT:

As an alternative, you can fake it with something like this if you know you are only going to use it in a specific way.

#lang racket

(require (for-syntax syntax/parse))

(define-syntax (with-hash stx)
  (syntax-parse stx #:datum-literals (check-equal?)
    [(_ h:expr (check-equal? key:id val:expr) ...)
     #'(let ([keys (hash-keys h)])
         (check-true (hash-has-key? h (quote key))) ...
         (check-equal? (hash-ref h (quote key)) val) ...)]))

(require rackunit)
(define h (hash 'id 1 'name "scott"))
(with-hash h
  (check-equal? id 1)
  (check-equal? name "scott"))

I'd suggest going the other direction and sticking with providing the identifiers. It is always a bit suspicious when identifiers are created/added into the evaluation environment in a Scheme program. Yes it is allowed and can be done safely, but it confuses ones understanding of what is bound, when and where.

So instead I'd suggest thinking of your with-hash as a binding construct which allows access to the fields in hash. Used like this:

(with-hash h ((the-id 'id) (the-name 'name)) ...)

or, using the default names,

(with-hash h (id name) ...)

It would be implemented like this:

(define-syntax with-hash
  (syntax-rules ()
    ((_ "gen" hash ((fname fkey) ...) body ...)
     (let ((obj hash))
       (let ((fname (hash-ref obj 'fkey)) ...)
         body ...))))
    ...
    ))
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top