Fully-expanding a define form in its definition context

133 views
Skip to first unread message

Scott Moore

unread,
May 4, 2015, 12:01:24 PM5/4/15
to Racket Users
Hi,

I'm trying to write a macro that fully-expands define forms with the goal of doing some static checking (and thus want the code in racket core so that I know what I'm looking at). Ideally, this macro would work in any context where a define form works. Unfortunately, I'm having a great deal of difficulty using local-expand to get what I want. I've tried a few approaches---the code and simple examples are below.

Local expanding as a 'top-level' definition was closest to working because it both fully-expands the definition and inserts binding into the enclosing definition context. Unfortunately, it seems to fail to bind the identifier correctly in the body of its own define.

I tried expanding in the 'syntax-local-context', but this causes define-values to complain that it isn't in a definition context (even though syntax-local-context is a module context, which I thought was a definition context).

Creating a new definition context makes the expansion work, but the binding goes into the newly created context, which isn't what I want. It also doesn't fully expand the definition. (If I remove define-values from the stop list, it tries to fully-expand but dies complaining that define-values is not in a definition context).

I think I'm probably misreading the documentation. Can anyone suggest a correct solution?

Thanks,
Scott

#lang racket

(require (for-syntax syntax/parse
                     syntax/parse/lib/function-header))

(define-for-syntax (definition-local-context-expand stx)
  (local-expand stx (syntax-local-context) '()))

(define-syntax (define-expand-top-level stx)
  (syntax-parse stx
    #:literals (define-expand-top-level)
    [(define-expand-top-level header:function-header body ...+)
     (local-expand #'(define header body ...) 'top-level '())]))

(define-syntax (define-expand-local-context stx)
  (syntax-parse stx
    #:literals (define-expand-local-context)
    [(define-expand-local-context header:function-header body ...+)
     (local-expand #'(define header body ...) (syntax-local-context) '())]))

(define-syntax (define-expand-new-context stx)
  (syntax-parse stx
    #:literals (define-expand-new-context)
    [(define-expand-new-context header:function-header body ...+)
     (let* ([def-ctx (syntax-local-make-definition-context)]
            [ctx (if (list? (syntax-local-context))
                     (cons (gensym) (syntax-local-context))
                     (list (gensym)))]
            [expd (local-expand #'(define header body ...) ctx (list #'define-values) def-ctx)])
       (define ids (syntax-parse expd [(def (id) _) (list #'id)]))
       (syntax-local-bind-syntaxes ids #f def-ctx)
       (internal-definition-context-seal def-ctx)
       expd)]))

; Works as expected
(define-expand-top-level (foo n)
  (+ n 1))

(foo 5)

; Fails to bind fib in the body of the define
#;(define-expand-top-level (fib n)
  (if (<= n 1)
      1
      (+ (fib (- n 1)) (fib (- n 2)))))
; fib: unbound identifier in module in: fib

; Attempt to use the local-context (which should be a definition context?)
#;(define-expand-local-context (fib n)
  (if (<= n 1)
      1
      (+ (fib (- n 1)) (fib (- n 2)))))
; define-values: not in a definition context in:
; (define-values (fib) (lambda (n) (if (<= n 1) 1 (+ (fib (- n 1)) (fib (- n 2))))))

; Creating new definition context makes the expand work but doesn't expose
; the identifiers to the definition context I really want
(define-expand-new-context (fib n)
  (if (<= n 1)
      1
      (+ (fib (- n 1)) (fib (- n 2)))))

#;(fib 3)
; fib: unbound identifier in module in: fib

Matthew Flatt

unread,
May 7, 2015, 3:01:00 PM5/7/15
to Scott Moore, Racket Users
The problem that you're hitting is that the `#%module-begin` form of
`racket` forces partial expansion of the module body to detect
definitions before it proceeds to expand expressions. That's why
`define-values` complains about a full expansion in context; it's not
supposed to be fully expanded, but instead recognized intact by the
`module` expander.

The only way to cooperate with that expander is to similarly stop
expansion at definition forms. For example, if you mainly want to
analyze expressions, your expanding macro would need to pass a
non-empty stop list to `local-expand`, recognize `define-values` forms
that come back from local expansion, and wrap the right-hand side with
another macro that can finish forcing expansion and analyze the
resulting expression.

If the analysis mainly needs to look at expressions but also have
information about definitions, then the partial-expanding macro could
expand to the definition plus a `define-syntax` so that information
about the definition can be found in the expression-analysis pass.

If your analysis needs to see the whole module body --- all definitions
and expressions together at once --- then you could replace
`#%module-begin` to take full control over the way the module body is
expanded. That's easier in some ways, but it's some work to support all
the definition-and-use patterns that the usual `#%module-begin` allows.
> --
> 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.
> For more options, visit https://groups.google.com/d/optout.
Reply all
Reply to author
Forward
0 new messages