Ale*_*ing 33 evaluation scheme racket quote
我知道你可以使用'(aka quote)创建一个列表,我一直都在使用它,如下所示:
> (car '(1 2 3))
1
Run Code Online (Sandbox Code Playgroud)
但它并不总是像我期望的那样工作.例如,我试图创建一个函数列表,像这样,但它不起作用:
> (define math-fns '(+ - * /))
> (map (lambda (fn) (fn 1)) math-fns)
application: not a procedure;
expected a procedure that can be applied to arguments
given: '+
Run Code Online (Sandbox Code Playgroud)
当我使用时list,它的工作原理:
> (define math-fns (list + - * /))
> (map (lambda (fn) (fn 1)) math-fns)
'(1 -1 1 1)
Run Code Online (Sandbox Code Playgroud)
为什么?我认为'这只是一个方便的速记,为什么行为不同?
Ale*_*ing 72
list有疑问时使用经验法则:list每当您想要评估参数时使用; quote"分配"其论点,所以'(+ 1 2)就像(list '+ '1 '2).您最终会在列表中使用符号,而不是函数.
list和quote在流程和球拍,quote并且list是完全不同的事情,但由于它们的可用于生产单,混乱是常见的,可以理解的.它们之间有一个非常重要的区别:list是一个普通的旧函数,而quote(即使没有特殊的'语法)是一种特殊的形式.也就是说,list可以在普通的Scheme中实现,但quote不能.
list功能list到目前为止,这个功能实际上只是两者中的简单,所以让我们从那里开始.它是一个接受任意数量参数的函数,它将参数收集到列表中.
> (list 1 2 3)
(1 2 3)
Run Code Online (Sandbox Code Playgroud)
上面的例子可能会quote让人感到困惑,因为结果被打印为一个能够表达式,而且在这种情况下,两个语法是等价的.但如果我们稍微复杂一些,你会发现它有所不同:
> (list 1 (+ 1 1) (+ 1 1 1))
(1 2 3)
> '(1 (+ 1 1) (+ 1 1 1))
(1 (+ 1 1) (+ 1 1 1))
Run Code Online (Sandbox Code Playgroud)
这个quote例子中发生了什么?好吧,我们稍后会讨论这个问题,但首先,请看一下list.它只是一个普通的函数,因此它遵循标准的Scheme评估语义:它在传递给函数之前计算每个参数.这意味着类似的表达式(+ 1 1)将被2收集到列表之前.
向列表函数提供变量时,此行为也可见:
> (define x 42)
> (list x)
(42)
> '(x)
(x)
Run Code Online (Sandbox Code Playgroud)
使用list,在x传递之前评估获得list.随着quote,事情变得更加复杂.
最后,因为list它只是一个函数,它可以像任何其他函数一样使用,包括以更高阶的方式.例如,它可以传递给map函数,它将正常工作:
> (map list '(1 2 3) '(4 5 6))
((1 4) (2 5) (3 6))
Run Code Online (Sandbox Code Playgroud)
quote表格与此不同list,引用是Lisps的一个特殊部分.该quote形式在部分特殊的,因为它得到专门的阅读器的简称,'但它也是特殊的,即使没有这一点.不同于list,quote它不是一个函数,因此它不需要像一个函数 - 它有自己的规则.
在Lisp中,Scheme和Racket是衍生品,所有代码实际上都是由普通的数据结构组成.例如,请考虑以下表达式:
(+ 1 2)
Run Code Online (Sandbox Code Playgroud)
该表达式实际上是一个列表,它有三个元素:
+符号12所有这些值都是程序员可以创建的正常值.创建1值非常容易,因为它会对自身进行求值:您只需键入1.但符号和列表更难:默认情况下,源代码中的符号执行变量查找!也就是说,符号不是自我评估的:
> 1
1
> a
a: undefined
cannot reference undefined identifier
Run Code Online (Sandbox Code Playgroud)
事实证明,符号基本上只是字符串,实际上我们可以在它们之间进行转换:
> (string->symbol "a")
a
Run Code Online (Sandbox Code Playgroud)
列表甚至比符号更多,因为默认情况下,源代码中的列表会调用函数!否则(+ 1 2)在列表中的第一个元素的外观,该+符号,查找与之相关的功能,并在列表中的其余元素调用它.
但有时,您可能希望禁用此"特殊"行为.您可能只想获取列表或获取符号而不进行评估.为此,您可以使用quote.
考虑到这一切,很明显是什么quote:它只是"关闭"它包装的表达式的特殊评估行为.例如,考虑quote一个符号:
> (quote a)
a
Run Code Online (Sandbox Code Playgroud)
同样,考虑quote一个列表:
> (quote (a b c))
(a b c)
Run Code Online (Sandbox Code Playgroud)
无论你给予什么quote,它总会一直向你吐出来.不多也不少.这意味着如果你给它一个列表,那么子表达式都不会被评估 - 不要指望它们是!如果您需要任何类型的评估,请使用list.
现在,有人可能会问:如果您quote不是符号或列表,会发生什么?嗯,答案是......什么都没有!你回来了.
> (quote 1)
1
> (quote "abcd")
"abcd"
Run Code Online (Sandbox Code Playgroud)
这是有道理的,因为quote仍然只是吐出你给它的确切内容.这就是为什么像数字和字符串这样的"文字"在Lisp用语中有时被称为"自引用".
还有一件事:如果你quote的表达式包含什么会发生什么quote?那就是,如果你"加倍quote"怎么办?
> (quote (quote 3))
'3
Run Code Online (Sandbox Code Playgroud)
那里发生了什么?好吧,记住这'实际上只是一个直接的缩写quote,所以没有什么特别的事情发生!事实上,如果您的Scheme有办法在打印时禁用缩写,它将如下所示:
> (quote (quote 3))
(quote 3)
Run Code Online (Sandbox Code Playgroud)
不要quote因为特殊而被愚弄:就像(quote (+ 1)),这里的结果只是一个简单的旧列表.事实上,我们可以从列表中获取第一个元素:你能猜出它会是什么吗?
> (car (quote (quote 3)))
quote
Run Code Online (Sandbox Code Playgroud)
如果你猜对了3,那你错了.请记住,quote禁用所有评估,包含quote符号的表达式仍然只是一个普通列表.在REPL中玩这个,直到你对它感到满意为止.
> (quote (quote (quote 3)))
''3
(quote (1 2 (quote 3)))
(1 2 '3)
Run Code Online (Sandbox Code Playgroud)
报价非常简单,但由于它往往无视我们对传统评估模型的理解,因此它可能会变得非常复杂.事实上,它很简单,因为它有多么简单:没有特殊情况,没有规则.它只是准确地返回你给它的东西,正如所述(因此称为"引用").
因此,如果报价完全禁用评估,它有什么用呢?好吧,除了提前提前知道的字符串,符号或数字列表之外,并不多.幸运的是,quasiquotation的概念提供了一种打破报价并重新进入普通评估的方法.
基础知识非常简单:不使用quote,而是使用quasiquote.通常情况下,这与quote各种方式完全相同:
> (quasiquote 3)
3
> (quasiquote x)
x
> (quasiquote ((a b) (c d)))
((a b) (c d))
Run Code Online (Sandbox Code Playgroud)
是什么让quasiquote特别是识别特殊符号unquote.无论unquote出现在列表中的哪个位置,它都会被它包含的任意表达式替换:
> (quasiquote (1 2 (+ 1 2)))
(1 2 (+ 1 2))
> (quasiquote (1 2 (unquote (+ 1 2))))
(1 2 3)
Run Code Online (Sandbox Code Playgroud)
这使您可以使用quasiquote构建具有"孔"的类别的模板来填充unquote.这意味着可以在引用列表中实际包含变量值:
> (define x 42)
> (quasiquote (x is: (unquote x)))
(x is: 42)
Run Code Online (Sandbox Code Playgroud)
当然,使用quasiquote和unquote相当冗长,所以他们有自己的缩写,就像'.具体来说,quasiquote是`(反引号)并且unquote是,(逗号).使用这些缩写,上面的例子更加可口.
> `(x is: ,x)
(x is: 42)
Run Code Online (Sandbox Code Playgroud)
最后一点:quasiquote实际上可以使用一个相当毛茸茸的宏在Racket中实现,它就是.它扩大到的用途list,cons以及当然quote.
list和quote计划list由于"rest argument"语法的工作原理,实现非常简单.这就是你所需要的:
(define (list . args)
args)
Run Code Online (Sandbox Code Playgroud)
而已!
相比之下,quote要困难得多 - 实际上,这是不可能的!这看起来完全可行,因为禁用评估的想法听起来很像宏.然而,一个天真的尝试揭示了麻烦:
(define fake-quote
(syntax-rules ()
((_ arg) arg)))
Run Code Online (Sandbox Code Playgroud)
我们只是采取arg并吐出来......但这不起作用.为什么不?好吧,我们的宏的结果将被评估,所以一切都是徒劳的.我们可以quote通过扩展到(list ...)并递归引用元素来扩展到某种类似的东西,如下所示:
(define impostor-quote
(syntax-rules ()
((_ (a . b)) (cons (impostor-quote a) (impostor-quote b)))
((_ (e ...)) (list (impostor-quote e) ...))
((_ x) x)))
Run Code Online (Sandbox Code Playgroud)
不幸的是,如果没有程序宏,我们就无法处理符号quote.我们可以近距离使用syntax-case,但即便如此,我们只会模仿quote行为,而不是复制它.
在Racket中的这个答案中尝试这些例子时,您可能会发现它们没有像预期的那样打印.通常,他们可以使用前导打印',例如在此示例中:
> (list 1 2 3)
'(1 2 3)
Run Code Online (Sandbox Code Playgroud)
这是因为默认情况下,Racket会在可能的情况下将结果打印为表达式.也就是说,您应该能够将结果输入到REPL中并获得相同的值.我个人认为这种行为很好,但在尝试理解报价时可能会让人感到困惑,所以如果你想将其关闭,调用(print-as-expression #f)或更改打印样式以便在DrRacket语言菜单中"写入".
您所看到的行为是Scheme 不将符号视为函数的结果。
该表达式'(+ - * /)产生一个值,该值是符号列表。这只是因为(+ - * /) 是一个符号列表,我们只是引用它来抑制求值,以便从字面上获取该对象作为值。
该表达式(list + - * /)生成一个函数列表。这是因为它是一个函数调用。符号表达式list、+、-和被求值*。/它们都是表示函数的变量,因此被简化为这些函数。然后调用该list函数,并返回其余四个函数的列表。
在 ANSI Common Lisp 中,将符号作为函数调用是可行的:
[1]> (mapcar (lambda (f) (funcall f 1)) '(+ - * /))
(1 -1 1 1)
Run Code Online (Sandbox Code Playgroud)
当在需要函数的地方使用符号时,符号的顶级函数绑定将被替换(如果有的话),一切都很酷。实际上,符号是 Common Lisp 中可函数调用的对象。
如果您想使用list来生成符号列表,就像 一样'(+ - * /),您必须单独引用它们以抑制它们的求值:
(list '+ '- '* '/)
Run Code Online (Sandbox Code Playgroud)
回到计划世界,您会发现,如果您map超过了这一点,它将以与原始引用列表相同的方式失败。原因是相同的:尝试将符号对象用作函数。
您显示的错误消息具有误导性:
expected a procedure that can be applied to arguments
given: '+
Run Code Online (Sandbox Code Playgroud)
这里'+显示的是(quote +). 但这并不是申请的内容。它只是给出的+,问题是符号对象+不能用作该方言中的函数。
这里发生的事情是诊断消息正在+“打印为表达式”模式下打印符号,这是 Racket 的一个功能,我猜你正在使用它。
在“打印为表达式”模式下,使用必须读取和评估以生成类似对象的语法来打印对象。请参阅 StackOverflow 问题“为什么 Racket 解释器在前面编写带有撇号的列表? ”