【问题标题】:Still about quote in Lisp仍然是关于 Lisp 中的引用
【发布时间】:2013-11-25 14:40:55
【问题描述】:

我是一个Lisp新手,业余时间慢慢学习……几个月前,我被一个Lisp REPL的错误报告弄糊涂了,下面的表达式不起作用:

((if (> 2 1) + -) 1 2)

环顾四周,我知道 Lisp 不是 Scheme……在 Lisp 中,我需要这样做:

(funcall (if (> 2 1) '+ '-) 2 1),或

(funcall (if (> 2 1) #'+ #'-) 2 1)

我还浏览了有关 lisp-1 和 lisp-2 的介绍性材料,虽然我无法将那里的所有内容全部归类...无论如何,我知道 quote 会阻止评估,作为一个例外到评估规则。

最近我在读一些关于reduce...然后作为练习,我想编写我自己的reduce 版本。虽然我设法让它工作(至少看起来工作),但我意识到我仍然无法准确解释为什么在 defun 的正文中,有些地方需要 funcall,而有些地方不需要。

以下是elisp中的myreduce

    (defun myreduce (fn v lst)
    (cond ((null lst) v)
          ((atom lst) (funcall fn v lst))
          (t (funcall fn (car lst) (myreduce fn v (cdr lst))))))

(myreduce '+ 0 '(1 2 3 4))

我的问题是关于第 3 行和第 4 行的:

  1. 第三行:为什么我需要funcall?为什么不只是(fn v lst)?我的“论点”是在(fn v lst) 中,fn 是列表中的第一个元素,因此 lisp 可能能够使用此位置信息将其视为函数……但事实并非如此。所以我当然错过了一些东西。

  2. myreduce 的递归调用中的第 4 行:什么样的fn 被传递给myreduce 的递归调用? '++,还是别的什么?

我想应该有一些我不知道的非常基本的东西......我想知道,当我调用myreduce 时,如第 6 行/最后一行所示,之后到底发生了什么(至少关于'+ 被传递),有没有办法在任何 REPL 环境中跟踪它?

非常感谢,

/布鲁因

【问题讨论】:

  • 用于跟踪使用TRACE

标签: lisp quote


【解决方案1】:

Common Lisp 是一个 LISP-2,有两个命名空间。一种用于函数,一种用于变量。参数绑定在变量命名空间中,因此fn 不存在于函数命名空间中。

(fn arg) ; call what fn is in the function namespace
(funcall fn ...) ; call a function referenced as a variable

'+ 是一个符号,当funcallapply 看到它是一个符号而不是函数对象时,它将在全局函数命名空间中查找它。 #'+(function +) 的缩写,它从本地函数命名空间解析函数。有很多调用#'+'+ 快,因为'+ 需要查找。符号和函数都可以作为 fn 传递给 myreduce,传递的内容与第 4 行中传递的内容相同。

(myreduce '+ 0 '(1 2 3 4)) ; here funcall might lookup what '+ is every time (CLISP does it while SBLC caches it)
(myreduce #'+ 0 '(1 2 3 4)); here funcall will be given a function object looked up in the first call in all consecutive calls

现在如果你传递'+,它将被评估为+并绑定到fn。 在myreduce 中,我们在递归中传递fn,它也会被评估为+

对于#'+,它计算为函数并绑定到fn。 在myreduce 中,我们在递归中传递fn,它将被评估为在变量命名空间中绑定的函数对象fn

Common Lisp 具有添加到函数命名空间的构造。例如。

(flet ((double (x) (+ x x))) ; make double in the function namespace
  (double 10)) ; ==> 20

但你本可以编写它并在变量命名空间上使用它:

(let ((double #'(lambda (x) (+ x x)))) ; make double in the variable namespace
  (funcall double 10))

【讨论】:

  • "(reduce '+ 0 '(1 2 3 4)) 中声称“funcall 每次都会查找 '+ 是什么”的理由是什么?为什么reduce 不能实现为(defun reduce (fn-designator ...) (let ((fn (designated-function fn-designator))) ...only use fn, not fn-designator...))?我实际上有点 expect 一种实现只查找指定的函数 once,并缓存结果(但我并不是说这实际上是,会发生什么)。
  • 我只是看了一下 SBCL 的源代码,看起来并没有进行这种优化,但我看不出它在哪里被规范禁止,要么……
  • @JoshuaTaylor 这不是来自 OP 的 reduce 而是 myreduce。此外,可以让reduce 修复传递的函数,以防它是symbolp,但这可能会破坏程序员的意图。也许函数会自我变异?让程序员选择添加一个字符并发送#'+(symbol-function '+) 以获取全局定义要容易得多。
  • 如果可能,那么打算在迭代期间更改符号的函数定义的程序员将具有特定行为,这取决于未来实现优化可能会破坏的某些未定义行为。我会责怪程序员。 :)
  • 无论如何,关键是要解决(myreduce '+ 0 '(1 2 3 4)) ; here funcall will lookup what '+ is every time中的评论是否准确。规范说,实现可以选择每次查找它,也可以选择只查找一次。至少一个实现(SBCL)只查找一次,并缓存该值。所以虽然funcall will lookup what '+ is every time一些 实现中可能是正确的,但在Common Lisp 中却不是这样。
【解决方案2】:

Common Lisp 有两个(实际上不止两个)命名空间:一个用于变量,一个用于函数。这意味着一个名称可以根据上下文表示不同的含义:它可以是变量,也可以是函数名称。

(let ((foo 42))    ; a variable FOO
  (flet ((foo (n) (+ n 107)))   ; a function FOO
    (foo foo)))    ; calling function FOO with the value of the variable FOO

如何定义变量的一些示例:

(defun foo (n) ...)   ; n is a variable
(let ((n 3)) ...)     ; n is a variable
(defparameter *n* 41) ; *n* is a variable

因此,无论何时定义和使用变量,名称都在变量命名空间中。

函数定义:

 (defun foo (n) ...)         ; FOO is a function
 (flet ((foo (n) ...)) ...)  ; FOO is a function

因此,无论何时定义和使用函数,名称都在函数命名空间中。

由于函数本身是一个对象,因此您可以将函数作为变量值。如果要调用这样的值,则需要使用 FUNCALL 或 APPLY。

  (let ((plus (function plus)))
    (funcall plus 10 11)) 

现在为什么会这样? ;-)

  • 两个命名空间允许我们将名称用作已经是函数的变量。

示例:在 Lisp-1 中我不会写:

(defun list-me (list) (list list))

在 Common Lisp 中,上述代码没有冲突。

  • 单独的函数命名空间使编译后的代码更简单:

在调用(foo 42) 中,名称 FOO 只能是未定义的,或者它是一个函数。不存在另一种选择。所以在运行时,我们永远不必检查 FOO 的函数值是否实际上是一个函数对象。如果 FOO 有一个函数值,那么它一定是一个函数对象。原因是:在 Common Lisp 中,不能用函数以外的东西来定义函数。

在Scheme中你可以写:

(let ((list 42))
  (list 1 2 3 list))

上面需要在某个时候检查,会导致错误,因为 LIST 是 42,这不是一个函数。

在 Common Lisp 中,上面的代码只定义了一个变量 LIST,但函数 LIST 仍然可用。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-14
    • 1970-01-01
    • 2012-06-16
    • 2011-04-08
    • 1970-01-01
    • 2014-09-02
    • 1970-01-01
    相关资源
    最近更新 更多