I've encountered an identifier binding order issue that only manifests in the REPL. My current workaround is to use forward definitions followed by set!s. I've heard rumors that the top-level is hopeless, but I'd like to try and make this work without unnecessary workarounds, if possible.
To demonstrate the issue, I've defined a syntax transformer that binds temporary names to procedures, and defines wrapper syntax transformers for referencing these procedures.
This syntax works fine within a module, or other non-top-level definition context. But when used at the top-level, I get an unbound identifier error as the first procedure body is being expanded. The first procedure references the second via the wrapper.
;; issue.rkt
#lang racket/base
(provide issue-syntax)
(require (for-syntax racket/base))
(define-syntax (issue-syntax stx)
(syntax-case stx ()
((_ ((name param ...) body ...) ...)
(with-syntax (((name.r ...) (generate-temporaries #'(name ...))))
#'(begin (define-syntax (name stx)
(syntax-case stx ()
((_ . args) #'(name.r . args))
(_ #'name.r))) ...
(define name.r (lambda (param ...) body ...)) ...)))))
;; eof
> racket
Welcome to Racket v8.0 [cs].
> (require "issue.rkt")
> (let ()
(issue-syntax
((foo x) (bar x 1 2)) ; note the reference to bar
((bar a b c) `(bar: ,a ,b ,c)))
(foo 'is-the-top-level-hopeless?))
(bar: is-the-top-level-hopeless? 1 2)
> (issue-syntax
((foo x) (bar x 1 2)) ; note the reference to bar
((bar a b c) `(bar: ,a ,b ,c)))
; bar4: unbound identifier;
; also, no #%top syntax transformer is bound
; in: bar4
; [,bt for context]
> ,bt
; bar4: unbound identifier;
; also, no #%top syntax transformer is bound
; in: bar4
; context...:
; /Applications/Racket v8.0/share/pkgs/xrepl-lib/xrepl/xrepl.rkt:1493:0
; /Applications/Racket v8.0/collects/racket/repl.rkt:11:26
I can work around this issue by altering issue-syntax to forward-define names before using set! to initialize them:
(define-syntax (issue-syntax stx)
(syntax-case stx ()
((_ ((name param ...) body ...) ...)
(with-syntax (((name.r ...) (generate-temporaries #'(name ...))))
#'(begin (define-syntax (name stx)
(syntax-case stx ()
((_ . args) #'(name.r . args))
(_ #'name.r))) ...
(define name.r #f) ... ; forward definitions
(set! name.r (lambda (param ...) body ...)) ...)))))
Is there a better alternative?