首页 > 解决方案 > 你如何引用一个隐藏的顶级变量?

问题描述

#lang racket

(define (shadowed x)
  'the-normal-result)

(define (f . args)
  (define (shadowed x)
    (cons 'local-extra (top-level-shadowed x)))
  (for/list ([arg args])
    (shadowed arg))) ;I want to be careful that I don't accidentally call the top-level shadowed func here

如上所示,有时我想在本地隐藏顶级函数,因为我想调用一个变体,而变体应该调用顶级函数。

经验表明,给本地变体起一个变体名称,例如shadow^,会导致错误,因为我不小心从内部范围调用了顶级函数。

我如何才能“跳出”内部范围,或者以某种方式限定标识符以引用其在顶层或明确指定的模块中的绑定?

更新

我发现了一些看起来很有希望的东西:#%top。但是,当我尝试它时,这就是我得到的:

> (define (f x) (list 'top x))
> (define (g x)
    (define (f x) (cons 'inner ((#%top . 'f) x)))
    (cons 'here (f x)))
. #%top: not an identifier in: (quote f)
> (define (g x)
    (define (f x) (cons 'inner ((#%top . f) x)))
    (cons 'here (f x)))
?: free identifier found in linklet

标签: scoperacket

解决方案


最简单的情况:如果顶层变量来自另一个模块

如果顶层变量是从另一个模块导入的,就像在这个程序中一样:

; other-module.rkt
#lang racket
(provide shadowed)
(define (shadowed x)
  'the-normal-result)

; main-module.rkt
#lang racket
(require "other-module.rkt")

(define (f . args)
  (define (shadowed x)
    ; I want to use the shadowed identifier from the other module
    (cons 'local-extra (shadowed-from-other-module x)))
  (for/list ([arg args])
    (shadowed arg)))

你可以local-require这样使用:

(define (f . args)
  (define (shadowed x)
    (local-require "other-module.rkt")
    (cons 'local-extra (shadowed x)))
  (for/list ([arg args])
    (shadowed arg)))

更复杂的情况,使用范围集操作来引用其他范围

您可以创建宏declare-scopein-scope,它们的使用方式如下:

#lang racket
(declare-scope top)
(define x "outer")
(let ()
  (define x "inner")
  (in-scope top x))
;"outer"

此 API 的优点是,如果您将所有内容包装在更大的范围内,它仍然可以工作:

(declare-scope top)
(define x "outer")

(let ()
  (declare-scope mid)
  (define x "middle")

  (let ()
    (define x "inner")
    (in-scope mid x)))
;"middle"

在您的代码上下文中,它看起来像这样:

(declare-scope top)
(define (shadowed x)
  'the-normal-result)

(define (f . args)
  (define (shadowed x)
    (cons 'local-extra ((in-scope top shadowed) x)))
  (for/list ([arg args])
    (shadowed arg)))

这些宏可以使用范围集操作(特别是make-syntax-delta-introducer)来定义,如下所示:

#lang racket

(require syntax/parse/define)

(define-syntax-parser declare-scope
  [(_ name:id)
   #:with stx this-syntax
   ;; This saves the syntax object at compile-time, so that in-scope
   ;; can reference it later.
   #'(define-syntax name (quote-syntax stx))])

(define-syntax-parser in-scope
  #:track-literals
  [(_ scope exp/def)
   ;; This gets scope-stx, the syntax object that was saved when the
   ;; scope was declared.
   #:declare scope (static syntax? "scope")
   (define scope-stx (syntax-local-introduce (attribute scope.value)))
   ;; This delta introducer has the scopes that are:
   ;;  - in exp/def
   ;;  - but not in scope-stx
   (define delta (make-syntax-delta-introducer #'exp/def scope-stx))
   ;; This removes the scopes that are in delta.
   (delta #'exp/def 'remove)])

推荐阅读