a question related to bound-identifier=?

80 views
Skip to first unread message

Yongming Shen

unread,
Jul 25, 2019, 9:55:19 PM7/25/19
to Racket Users
Hi there,

Based on my understanding, (bound-identifier=? id-a id-b) only returns true if id-a would bind id-b AND id-b would bind id-a. Also based on my understanding, id-a will bind id-b doesn't imply that id-b will bind id-a. So, if I only want to check whether id-a will bind id-b, which function should I use?

Thanks,
Yongming

Matthew Flatt

unread,
Jul 26, 2019, 7:07:49 AM7/26/19
to Yongming Shen, Racket Users
There's not a predicate like that right now (I guess only because we
haven't needed it).

Here's one way to implement the predicate:

(define (would-bind? a b)
(subset? (list->set (hash-ref (syntax-debug-info a) 'context))
(list->set (hash-ref (syntax-debug-info b) 'context))))

A built-in operation would work the same way, except that it could be
more efficient by not serializing scopes to a list in debugging
information and then converting the list back to a set.


Matthew

Yongming Shen

unread,
Jul 29, 2019, 5:05:02 PM7/29/19
to Racket Users
I think I found a case where id-a binds id-b when (would-bind? id-a id-b) returns #f:

(define-syntax bind-test0

  (lambda (stx)

    (define id #'x)

    (define shifted-id (syntax-shift-phase-level id -1))

    (displayln (would-bind? id shifted-id))

    (syntax-case stx ()

      [(_ e)

        #`(let ([#,id e])

             (displayln #,shifted-id))])))


(bind-test0 100) would print #f then 100. However, if shifted-ld and id are swapped in (let ...), an undefined identifier error will be raised, which is consistent with the return value of (would-bind? ...). Is there a bug in how (let ...) handles identifiers with shifted phase levels?

Thanks,
Yongming

Matthew Flatt

unread,
Aug 2, 2019, 8:37:15 AM8/2/19
to Yongming Shen, Racket Users
This is certainly confusing.

The missing piece of the puzzle has to do with expansion in an
immediate module body. In that context, the macro expander adds the
module's "inside-edge" scope (unshifted) to the result of any macro
expansion. So, the expander is adding back the inside-edge scope that
is effectively removed by phase shifting in `bind-test0`.

If you use `bind-test0` in a more nested position, then it behaves the
way you expected:

(let ()
(bind-test0 5))


The addition of an inside-edge scope by the expander is the same as for
an internal definition context. See sections 1.2.3.8 and 1.2.3.9 in the
reference:

https://docs.racket-lang.org/reference/syntax-model.html?q=internal%20definition#%28part._intdef-body%29

The idea behind the inside-edge scope addition is to ensure that any
binding form that appears as during an expansion is constrained to bind
within the definition scope (i.e., not establish a binding for some
arbitrary other context). The affect on the `let` binding here seems
like an unfortunate side effect of the way that constraint is
implemented.
> --
> 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...@googlegroups.com.
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/racket-users/b1a999bb-cc3a-4da8-ae31-11e5a3bf
> f7d6%40googlegroups.com.

Yongming Shen

unread,
Aug 2, 2019, 6:53:57 PM8/2/19
to Racket Users
I see. Thank you for the clear and detailed explanation!
Reply all
Reply to author
Forward
0 new messages