I have a macro that does something that approximates binding. It looks
like this:

  (∀ [α] (→ α α))

Obviously, I’d really like it if the αs had binding arrows drawn to
them. The trouble, unfortunately, is that ∀ does not expand to a binding
form at all; it is parsed in a single macro step to a value composed of
prefab structs, which is then stashed in a preserved syntax property
somewhere. No identifier with the name α is ever bound.

It seems like the 'disappeared-binding and 'disappeared-use properties
are for precisely this purpose, but in my initial implementation, I
found that they had some problems for this use case. To illustrate,
consider the following macro:

  (define-syntax-parser bind
    [(_ binder:id use:id)
     (syntax-properties
      #'(void)
      (list (cons 'disappeared-binding
                  (syntax-local-introduce #'binder))
            (cons 'disappeared-use
                  (syntax-local-introduce #'use))))])

(Where syntax-properties is a simple function that attaches multiple
syntax properties from a dictionary.)

This works okay, since I can now do this:

  (bind α α)

…and DrRacket draws a binding arrow between the two αs. However, there
is a problem. If I do this:

  (bind α α)
  (bind α α)

…then DrRacket draws binding arrows between all the αs, not just each
pair, since they are all free-identifier=?. This is somewhat
problematic, since I don’t ever actually bind α, and the identifiers
will almost always be free-identifier=? if they have the same name.

What I really want is a way to draw arrows very deliberately. I want a
way to say “draw an arrow between these two identifiers, but not any
other ones”. Unfortunately, all of the syntax properties available
('disappeared-use, 'disappeared-binding, and 'sub-range-binders) depend
on some inspection of identifier bindings, as far as I can tell. To
solve this problem, I found a way to cheat: I can forge identifiers with
made-up source locations to get Check Syntax to do what I want:

  (define (binding-arrows-props binders uses)
    (define tmp (generate-temporary))
    (define (mk-tmp id)
      (propagate-original-for-check-syntax
       id (datum->syntax tmp (syntax-e tmp) id id)))
    (list (cons 'disappeared-binding (map mk-tmp binders))
          (cons 'disappeared-use (map mk-tmp uses))))

  (define (binding-arrows stx binders uses)
    (syntax-properties stx (binding-arrows-props binders uses)))

(Where propagate-original-for-check-syntax inspects a syntax object and
applies the 'original-for-check-syntax syntax property if necessary.)

Using these functions, I can invent identifiers that will “trick” Check
Syntax into drawing binding arrows between any sets of identifiers that
I desire. In fact, the endpoints of the arrows don’t even need to be
identifiers! I can do this:

  (define-syntax-parser bind
    [(_ a b)
     (binding-arrows
      #'(void)
      (list (syntax-local-introduce #'a))
      (list (syntax-local-introduce #'b)))])

…and Check Syntax will now happily draw arrows between absolutely
anything at all:

  (bind α α)
  (bind #s(point 1 2) 75)

This is kind of neat, but it seems like a horrible hack. Is there a
better way to get the arrows I want even if my macro is a little bit
naughty and simply emulates binding even when there isn’t any? Are there
any terrible pitfalls with the approach I’ve taken? For what it’s worth,
this isn’t hypothetical — this is an actual issue I’ve run into, and
this has been my kludge of a solution.

If everyone thinks this is a-ok, I’ll keep using it, but if there’s a
better way, I’d happily switch to something nicer.

(Also, as a final note, my apologies for asking so many questions about
the Check Syntax arrows on this list, but they’re really, really useful,
and I try my best to cooperate with them whenever I can.)

-- 
You received this message because you are subscribed to the Google Groups 
"Racket Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Reply via email to