5

我是一个完全的 Lisp n00b,所以请温柔一点。

我很难理解 CL 关于 [未] 声明的自由变量的想法。我会认为:

(defun test ()
    (setq foo 17)
)

将定义一个声明变量 foo 并将其设置为 17 的函数。但是,相反,我得到

;Compiler warnings :
;  In TEST: Undeclared free variable FOO

我的实际示例案例要大一些;我的代码(片段)如下所示:

(defun p8 ()
    ;;; [some other stuff, snip]

    (loop for x from 0 to (- (length str) str-len) do
        (setq last (+ x str-len))           ; get the last char of substring
        (setq subs (subseq str x last))     ; get the substring
        (setq prod (prod-string subs))      ; get the product of that substring
        (if (> prod max)                    ; if it's bigger than current max, save it
            (setq max prod)
            (setq max-str subs)
        )
    )

;;; [More stuff, snip]
)

这给了我:

;Compiler warnings for "/path/to/Lisp/projectEuler/p6-10.lisp":
;   In P8: Undeclared free variable LAST (2 references)
;Compiler warnings for "/Volumes/TwoBig/AllYourBits-Olie/WasOnDownBelowTheOcean/zIncoming/Lisp/projectEuler/p6-10.lisp" :
;   In P8: Undeclared free variable PROD (3 references)
;Compiler warnings for "/Volumes/TwoBig/AllYourBits-Olie/WasOnDownBelowTheOcean/zIncoming/Lisp/projectEuler/p6-10.lisp" :
;   In P8: Undeclared free variable SUBS (3 references)
;Compiler warnings for "/Volumes/TwoBig/AllYourBits-Olie/WasOnDownBelowTheOcean/zIncoming/Lisp/projectEuler/p6-10.lisp" :
;   In P8: Undeclared free variable =

是的,是的,我意识到我使用了太多的中间变量,但我试图了解发生了什么,然后再将所有内容压缩到最小的类型字符,这在 CL 世界中似乎很流行。

所以,无论如何......有人可以解释以下内容:

  • Lisp 在什么条件下“声明”一个变量?
  • 所述变量的范围不是(...)围绕setq语句的封闭吗?!(也就是说,我希望 var 对于 var(... (setq ...) ...)之外的 parens 1 级别的所有内容都是有效的setq,不是吗?
  • 我是否误解了未声明的自由变量消息?
  • 您愿意提供的任何其他提示将帮助我更好地了解发生了什么,在这里。

注意:我非常精通 C、Java、Javascript、Obj-C 和相关的过程语言。我知道函数式编程是不同的。现在,我只是在与语法搏斗。

谢谢!

PS 如果重要的话,它defun p8是在一个文本文件(TextMate)中,我正在运行它Clozure CL。不过,希望这些都不重要!

4

3 回答 3

10

在 lisp 中,变量可以使用defparameteror来声明defvar

(defparameter var1 5)
(defvar var2 42)

这会产生全局(动态)变量。

defvar和之间的区别在于defparameterdefvar不会重新初始化已经存在的变量。

局部(词法)变量被引入,例如使用letlet*(按顺序初始化变量)。

未声明的自由变量意味着您已使用(此处setq-ed)一个变量,该变量未绑定在使用的上下文中。然后可以为您声明它,但随后可能作为全局(动态)变量。这样做的结果是,如果您在多个函数中使用具有相同名称的未声明变量,您将在所有函数中引用相同的变量。

你的代码可以这样写:

(loop for x from 0 to (- (length str) str-len) do
    (let* ((last (+ x str-len))         ; get the last char of substring
           (subs (subseq str x last))   ; get the substring
           (prod (prod-string subs)))   ; get the product of that substring
      (if (> prod max)                    ; if it's bigger than current max, save it
          (setq max prod)
          (setq max-str subs))))

使用循环的变量绑定属性,它也可以写成

(loop for x from 0 to (- (length str) str-len)
      for last = (+ x str-len)
      for subs = (subseq str x last)
      for prod = (prod-string subs)
      when (> prod max) do
          (setq max prod)
          (setq max-str subs))
于 2013-07-13T07:02:09.237 回答
5

在 Lisp 中,变量声明可以以多种方式执行。最值得注意的是:

  • 用and声明全局(它们被恰当地称为special)变量defparameterdefvar
  • let用, let*, multiple-value-bind,destructuring-bind和其他绑定形式声明局部变量
  • 作为函数参数

您还可以在许多地方了解它们的范围,例如在CLtL2中。

setq/setf不是变量声明运算符,而是变量修改运算符,正如其名称所暗示的那样。

PS。在交互模式下,如果您尝试设置未声明的变量,某些实现将使用 DWIM 方法并在幕后将变量声明为特殊变量,但这纯粹是为了方便。

于 2013-07-13T07:01:35.570 回答
4

Common Lisp HyperSpec(基本上是 HTML 形式的 Common Lisp 标准)说:

http://www.lispworks.com/documentation/HyperSpec/Body/s_setq.htm

为变量赋值。

所以SETQ只给变量赋值。它没有声明它们。

变量定义通过DEFVAR, DEFPARAMETER, ...全局完成

(defparameter *this-is-a-global-dynamic-variable* 'yep)

变量定义通过DEFUN, LET, LET*,LOOP等在本地完成。

(defun foo (v1 v2)
  ...)

(let ((v1 10)
      (v2 20))
  ...)

(loop for v1 in '(10 30 10 20)
      do ...)

这是基本的 Lisp,阅读介绍会很有用。我会推荐:

http://www.cs.cmu.edu/~dst/LispBook/

以上书籍可免费下载。

此外,上面提到的 Common Lisp Hyperspec 为您提供了 Common Lisp 的定义,并详细描述了各种工具(DEFUN、LOOP、DEFPARAMETER,...)。

于 2013-07-13T07:02:18.877 回答