使'define'在Racket中计算其第一个参数

使'define'在Racket中计算其第一个参数,racket,Racket,在LISP的某些方言中,SET和SETQ之间存在区别,第一个参数计算其第一个参数,因此需要使用(SET(QUOTE…)语法 由于在Racket中,定义中不需要引用,define的行为类似于SETQ。 是否有类似于SET的球拍功能?如果没有,怎么写 我尝试了(define(SET a b)(define(eval a)b)b),但当使用(提供集)将其提供给其他语言时,它似乎不起作用。下面是我对这个问题的快速尝试: ;; lib.rkt #lang racket/base (provide (re

在LISP的某些方言中,
SET
SETQ
之间存在区别,第一个参数计算其第一个参数,因此需要使用
(SET(QUOTE…)
语法

由于在Racket中,定义中不需要引用,
define
的行为类似于
SETQ
。 是否有类似于
SET
的球拍功能?如果没有,怎么写


我尝试了
(define(SET a b)(define(eval a)b)b)
,但当使用
(提供集)
将其提供给其他语言时,它似乎不起作用。下面是我对这个问题的快速尝试:

;; lib.rkt
#lang racket/base

(provide (rename-out [@set set]
                     [@#%top #%top]
                     [@set! set!]
                     [@define define]))
(require syntax/parse/define)

(define env (make-hash))

(define (set x v stx)
  (unless (hash-has-key? env x)
    (raise-syntax-error #f "undefined id" stx))
  (hash-set! env x v))

(define-simple-macro (@#%top . x)
  (hash-ref
   env
   'x
   (λ () (raise-syntax-error #f "unbound id" (quote-syntax x)))))

(define (@set x v)
  (set x v x))

(define-simple-macro (@set! x:id v)
  (set 'x v (quote-syntax x)))

(define-simple-macro (@define x:id v)
  (begin 
    (when (hash-has-key? env 'x)
      (raise-syntax-error #f "id already defined" (quote-syntax x)))
    (hash-set! env 'x v)))
请注意,这不同于原来的球拍在几个方面。例如:

  • 未绑定的ID现在在运行时报告,而不是在编译时报告
  • set现在无法使用
    集-变压器
  • define
    不能用于定义函数
  • define
    不能用于隐藏标识符
  • 对于(2)和(3),可以恢复原始行为,但我不希望答案太长,因此我没有包含完整的功能。现在,我不知道如何解决(4)


    还请注意,您只能通过
    定义
    设置
    标识符。如果您想设置通过
    lambda
    let
    等定义的标识符,您也需要重新定义这些结构。

    我会做得更简单、更详细

    由于所有参数都经过计算,
    集合
    或者说
    define%
    可以定义为一个函数

    (define (define% x y)
        (eval `(define ,x ,y)))
    
    使用
    lambda
    使用旧式表单时,甚至可以使用
    define%
    定义函数

    (define 'ab (lambda (x y) (+ x y)))
    
    (ab 3 5) ;; 7
    
    它甚至在范围方面表现得很正确

    (define (foo x)
        (define% 'bar (lambda (x) (+ 1 x)))
        (bar (bar (bar x))))
    
    foo
    ;; #<procedure:foo>
    bar
    ; bar: undefined;
    ;  cannot reference undefined identifier
    ; [,bt for context]
    
    (foo 3)
    6
    
    ;; after first call however, bar is available in global environment
    ;; as pointed out by @AlexKnauf
    bar 
    ;; #<procedure:bar>
    
    (定义(foo x)
    (定义%巴(λ(x)(+1 x)))
    (巴(巴(巴x)))
    福
    ;; #
    酒吧
    ; 酒吧:未定义;
    ;  无法引用未定义的标识符
    ; [,上下文为bt]
    (富3)
    6.
    ;; 然而,在第一次调用之后,bar在全局环境中可用
    ;; 正如@AlexKnauf所指出的
    酒吧
    ;; #
    
    因此,存在一些范围界定问题

    (let ((x 0))                                                                                                                           
      (define% 'counter (lambda () (set! x (+ x 1)) x)))                                                                        
    counter
    ;; #<procedure>
    (counter)
    ;; 1
    (counter)
    ;; 2
    (counter)
    ;; 3
    
    (let((x 0))
    (定义%'计数器(lambda()(set!x(+x1))x)))
    柜台
    ;; #
    (柜台)
    ;; 1.
    (柜台)
    ;; 2.
    (柜台)
    ;; 3.
    
    Racket中重要的一点是变量绑定是在编译时确定的,而不是在运行时确定的,因此
    define
    变量不能在运行时“计算”。但是,可以在编译时使用宏对其求值。你能澄清一下你想要它做什么吗?我想要它是因为我正在使用Racket来构建一种教育语言,我需要一个
    SET
    函数如果我理解正确,在你的实现中,必须在SET和SET之前定义一个变量!可以使用命令吗?是的,
    就是这样设置的在球拍中工作。如果您不喜欢,那么请删除检查
    (除非(hash有key?env x)
    。我认为您应该警告
    eval
    的一些危险,这可能是初学者的一个陷阱,他们还不知道何时使用它,何时使用它不是一个好情况不-使用
    eval
    是安全的。当然,如果可以避免的话,就不应该使用
    eval
    。但在这种特殊情况下,
    eval
    是“好的”,我称之为“evalovermacrocall”。我在这里和Vsevolod Dyomkin讨论过。在我看来,
    eval
    的正确使用是一项资产。我认为在这种特殊情况下也是不安全的:答案中的第三个代码块在两种情况下中断:(a)当
    foo
    的定义放入一个模块时(仅在repl或更大的eval中有效),以及(b)在repl中,调用
    foo
    后它会中断:调用
    (foo 10)
    后,
    条在其作用域之外定义为true。。。在
    (foo 1)
    的第一个cll之后,
    条在外部可见。。。
    
    (let ((x 0))                                                                                                                           
      (define% 'counter (lambda () (set! x (+ x 1)) x)))                                                                        
    counter
    ;; #<procedure>
    (counter)
    ;; 1
    (counter)
    ;; 2
    (counter)
    ;; 3