setq和未声明的自由变量(Common Lisp)之间的区别

5

我是一个完全不懂Lisp的新手,请温柔一点。

我对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语句周围?!(也就是说,我希望从(... (setq ...) ...) var对于括号级别有效且范围为setq外部1级的所有内容,不是吗?
  • 我是否误解了“未声明的自由变量”消息?
  • 您还有其他提示可以帮助我更好地理解正在发生的事情。

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

谢谢!

P.S. 如果有关系,defun p8 在文本文件(TextMate)中,并且我正在Clozure CL上运行它。希望这些都不重要!

3个回答

10

在Lisp中,变量可以使用defparameterdefvar进行声明。

(defparameter var1 5)
(defvar var2 42)

这将导致全局(动态)变量。

defvardefparameter的区别在于,defvar不会重新初始化已经存在的变量。

局部(词法)变量可以通过使用letlet*引入(后者按顺序初始化变量)。

未声明的自由变量意味着您使用(此处是setq设置)了一个未在上下文中绑定的变量。它可能会为您声明变量,但很可能作为全局(动态)变量。这意味着,如果您在多个函数中使用同名的未声明变量,则会在所有函数中引用相同的变量。

您的代码可以像这样编写:

(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))

5
在Lisp中,变量声明可以通过多种方式完成。 最重要的是:
  • 使用defparameterdefvar声明全局(正确称为special)变量
  • 使用letlet*multiple-value-binddestructuring-bind和其他绑定形式声明局部变量
  • 作为函数参数
您也可以在许多地方阅读有关它们范围的信息,例如在CLtL2中。 setq/setf不是变量声明运算符,而是变量修改运算符,正如其名称所暗示的那样。
PS. 在交互模式下,一些实现会使用DWIM方法,在幕后将变量声明为special,如果您尝试设置未声明的变量,但这纯粹是为了方便。

4
“Common Lisp HyperSpec”(基本上是以HTML形式呈现的Common Lisp标准)中写道:

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

给变量赋值。

SETQ 只给变量赋值,不声明它们。

使用 DEFVARDEFPARAMETER 等全局定义变量。

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

变量定义可以通过DEFUNLETLET*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等)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接