【问题标题】:How to make a "define" that accepts string in the first parameter in SISC Scheme?如何在 SISC 方案的第一个参数中创建一个接受字符串的“定义”?
【发布时间】:2014-04-26 02:40:22
【问题描述】:

我们称这个函数为“动态定义”。

基本上我想写一个像这样工作的宏或 lambda:

$ (dynamic-define "variable" 123)
$ variable
$ => 123

我试过了:

(define-syntax dynamic-define
    (syntax-rules ()
      ((_ string <body>)
       (eval `(define ,(string->symbol string) <body>)))))

它按预期工作,但似乎不是一个好的解决方案。

我尝试像这样不使用 eval:

(define-syntax dynamic-define                                           
    (syntax-rules ()                                                            
      ((_ string <body>)                                                        
       (define (string->symbol string) <body>))))

但是当我尝试使用时,我得到了这个错误:

Error: invalid syntax (define (string->symbol "variable") 123)

我该怎么办?

(请:编辑此问题以适合母语英语并删除此行)

【问题讨论】:

    标签: scheme sisc


    【解决方案1】:

    您遇到了一个基本问题:您无法以“干净”的方式进行真正的动态定义,因此您尝试使用eval。请注意,上面的两个解决方案都不是动态的——它们给你的只是写"foo"而不是foo的能力。正如我在其他地方所说,使用eval 通常很糟糕,在这种情况下更糟,因为它是从宏中使用的eval,这使得它非常奇怪。这是除了在你的宏中有一个隐式的准引号之外,它会让事情变得更加混乱。你也可以将整个事情定义为一个普通的函数:

    (define (dynamic-define string <body>)
      (eval `(define ,(string->symbol string) ,<body>)))
    

    如果你真的需要它来工作,那么最好退后一步,想想你到底需要什么。一方面,上述解决方案不是动态的,因为它们需要使用带有字符串语法的宏

    (dynamic-define "foo" 123) ; instead of (define foo 123)
    

    但是如果有一个接受字符串的 real 动态定义会是什么样子呢?您可能希望这会定义 b:

    (define a "b")
    (dynamic-define a 123)
    

    但是当您考虑它如何与其他绑定交互时,这只会变得更加混乱。例如:

    (define y 123)
    (define (foo s)
      (define x 456)
      (dynamic-define s 789)
      (+ x y))
    

    现在,假设 dynamic-define 完成了您希望它执行的操作,请考虑使用 (foo "x")(foo "y") 会得到什么。更糟糕的是,考虑(foo "s")(foo "+")。更糟糕的是,

    (foo (random-element '("x" "y" "s" "+" "define")))
    

    ?显然,如果这个dynamic-define 真的做了一些动态定义,那么在不提前知道foo 将被调用的名称的情况下,你就可以编写这段代码是没有意义的。如果无法理解,编译就会消失——但更重要的是,正确性(或者通常,代码的含义)会消失。

    根据我的经验,使用哈希表和类似设备处理这种模式要好得多。

    【讨论】:

    • 感谢 Eli 的解释。但是如果我想做一些像 SRFI 9 这样的事情呢?他们没有使用哈希表、define-macro 或 syntax-case 之类的东西。他们只是使用非常常见的“语法规则”。但毕竟我可以使用“make-”之类的东西。
    • @FelipeMicaroniLalli:嗯,像为新数据类型定义新函数这样的事情当然是可能的,并且与哈希表的使用非常不同。但是,如果宏需要创建新名称,则无法使用普通的 syntax-rules 来完成。问题是,在 SRFI 9 中,您不需要创建新名称——这就是在新记录定义中指定访问器名称的原因,实际上这是有意完成的,因此有可能用简单的syntax-rules 宏定义它。
    【解决方案2】:

    使用define-macro

    #> (define-macro (dynamic-define varstr val)
          `(define ,(string->symbol varstr) ,val))
    
    #> (dynamic-define "variable" 123)
    #> variable
    123
    

    使用syntax-case

    #> (define-syntax dynamic-define
         (lambda (stx)
           (syntax-case stx ()
             ((k varstr val)
              (with-syntax ([var (datum->syntax-object 
                                  #'k
                                  (string->symbol (syntax-object->datum #'varstr)))])
                #'(define var val))))))
    
    #> (dynamic-define "variable" 123)
    #> variable
    123
    

    【讨论】:

    • SISC 还支持syntax-case(事实上,就像几乎所有使用syntax-case 的实现一样,它的define-macro 是使用syntax-case 定义的),因此更可取的是制定过程宏使用syntax-case。事实上,any 系统几乎都比define-macro 好。
    • @ChrisJester-Young 欢迎您根据自己的信念添加自己的答案。就我而言,卫生宏是 Scheme 严重错误的事情之一(Common Lisp 是正确的)。我也怀疑替代方案将是 1) 更简洁和 2) 对于“更好”的任何客观价值来说“更好”。尽管如此,我是开放的 - 让我惊讶!
    • @ChrisJester-Young 我添加了一个syntax-case 版本。我想这证实了我的观点;)
    • IMO define-macrosyntax-rulesyntax-case 有其特定的域。对于您的问题syntax-case 是矫枉过正。我喜欢 Greg Hendershott 的“Fear of Macros”,但由于我没有使用过 SISC,我不知道它适用于多少。不过还是不错的读物:greghendershott.com/fear-of-macros
    • @uselpa:无论是哪个版本,你得到的只是写(dyndef "foo" 1)而不是(def foo 1);从字面上看,该字符串必须在宏使用中存在——因此它并不比常规定义更具动态性。请参阅我的(非)答案中的解释。
    猜你喜欢
    • 2021-02-16
    • 2020-06-30
    • 1970-01-01
    • 1970-01-01
    • 2012-07-04
    • 1970-01-01
    • 1970-01-01
    • 2014-07-06
    • 2019-03-19
    相关资源
    最近更新 更多