函数的范围和函数内的变量和/或lisp的let语句

时间:2014-10-07 07:57:08

标签: scope lisp common-lisp

在函数调用中定义时,我很难理解变量和函数的范围。我试着搜索这个范围,但找不到合适的答案(或者可能是在寻找错误的东西),所以我决定写一些函数来自己测试一下:

(defun test-scope1 ()
  (setf myvar 1)
  (defun set-var1 ()
    (setf myvar 2))
  (set-var1))

使用此功能,我只想查看是否有任何设置为全局。我希望myvar和set-var能够全局定义,因为我这里没有范围。正如预期的那样,在调用(test-scope1)之前,命令myvar(set-var)会给我错误。致电(test-scope1)后,我可以在解释器中运行myvar(set-var)以获得2。

想一想我的变量封装会很好,我决定在我的函数中添加一个let,因此得到我的下一个测试:

(defun test-scope2 ()
  (let ((myvar 10))
    (defun set-var2 ()
      (setf myvar 20))
    (set-var2)))

我希望myvar能够停留在let块的范围内,但无法猜测set-var2。它可以卡在let块中,也可以全局定义。运行(test-scope2)后,我尝试访问myvar并获得2.这意味着这个新函数有自己的myvar,因为它仍然是前一个函数的2。我尝试运行(set-var2)并获得20。

我在let块中运行后全局定义函数并不完全感到惊讶,但现在我很困惑myvar变量的访问权限。由于它没有改变myvar的全局副本,看起来它周围的一些变量仍然是指的。

现在我想看看我是否可以操纵那个浮动变量,所以我创建了第三个函数。

(defun test-scope3 ()
  (let ((myvar (if (ignore-errors myvar)
                   myvar
                   100)))
    (defun set-var3 ()
      (setf myvar (+ myvar 100)))
    (set-var3)))

我不想仅仅将变量设置为固定值,而是根据之前的值递增它。我在这里检查两件事。第一个是调用test-scope3的时候。我想知道我是否可以提高以前的价值" myvar,因为如果它漂浮在某个地方,我可以再次访问它。它可能不是一个好习惯,但这不是重点。如果没有真正的前一个值浮动,则忽略错误,在这种情况下,我选择默认值为100。

我测试的第二件事是让set-var3将myvar的值加100。我想看看这是否会调整浮动变量,或者某种方式是静态的。我不知道我的功能"应该"返回。

运行(test-scope3)后,我完全惊讶地看到102.显然,我的测试范围3通过运行test-scope1找到了myvar的值。但是,在检查解释器中myvar的值之后,它仍然是2.接下来我运行(set-var3)并获得202的返回值。好的,所以它再次添加100到我之前的值。再次调用它会返回302,依此类推。但是,再次调用(test-scope3)会将此值重置为102。

我写了另一个函数作为双嵌套let命令。我只是以两种方式运行它:没有myvar定义的let参数。此函数返回10002.然后,我尝试将本地myvar设置为50并返回1050。

(defun test-scope4 ()
  (let () ; or ((myvar 50))
    (let ((myvar (if (ignore-errors myvar)
                      myvar
                      2000)))
      (defun set-var4 ()
        (setf myvar (+ myvar 1000)))
    (set-var4))))

所以,综合这一切,以下是我的一些具体问题:

  1. 当我在另一个defun中进行defun时,即使在let block中,为什么这个函数可以在全局访问?
  2. 当我调用set-var#时,这个访问是什么变量(或在什么范围内)?或者更恰当的是,当我在另一个语句中定义一个函数时,我实际上绑定了什么?
  3. 当我在函数中使用变量myvar时,这会从何而来?从我的第四个例子推测,它在当前范围内查找符号myvar,然后检查一个更高的级别,直到找到它的值。

  4. 很抱歉,如果一切都很罗嗦,我的问题也不正确。我尽力调查事情。真的这一切都导致了我真正的问题,在写完所有内容之后,我意识到可能超出了这个问题的范围(没有双关语意图),因为我已经设置了它。

    隐藏上面给出的内部函数的问题可以用lambda表达式处理;但是,我真正喜欢做的是在更大的块内部使用递归函数,该块使用块来存储值而不将它们直接送入函数。据我所知,使用lambda表达式是不可能的。例如,考虑以下函数来执行此操作。

    (defun outer-function (start)
      (let ((x start))
        (defun increment-to-ten ()
          (setf x (+ x 1))
          (if (< x 10)
              (increment-to-ten)))
        (increment-to-ten)
        (print x)))
    

    可以使用参数

    递归实现
    (defun increment-to-ten-recursive (x)
      (if (< x 10)
          (increment-to-ten-recursive (+ x 1))
          10))
    

    如果有解决方案,那就太好了,或者如果我的想法完全错误,并且有更好的方法来做到这一点,那将是很好的。为您创建块存储数据似乎很方便,然后只调用没有参数的递归函数来处理该数据。

2 个答案:

答案 0 :(得分:5)

为什么很难讨论您看到的效果:

您正在做Common Lisp中未定义的事情:在test-scope1myvar中设置未声明的变量。从那时起,不清楚以下代码的行为方式。

未声明的变量

未定义设置未声明变量foo时的效果。实现允许它。有些人会警告。 SBCL:

* (setf foo 10)
; in: SETF FOO
;     (SETF FOO 10)
; ==>
;   (SETQ FOO 10)
; 
; caught WARNING:
;   undefined variable: FOO
; 
; compilation unit finished
;   Undefined variable:
;     FOO
;   caught 1 WARNING condition

使用DEFPARAMETERDEFVAR定义全局变量。局部变量由LETLET*和函数参数定义。由于DEFPARAMETERDEFVAR定义了全局特殊(使用动态绑定)变量,因此将它们编写为*some-variable*是很常见的 - 请注意*周围的DEFUN它,它是符号名称的一部分,而不是特殊语法。这是特殊变量的惯例。

嵌套DEFUN

Common Lisp中未使用嵌套的DEFUNFLET是顶级表单,用于设置全局函数。要定义本地函数,请使用LABELSDEFUN。你可以嵌套DEFUN表单,但它是不好的样式,你不会发现它在实际的Lisp代码中使用。不要嵌套DEFINE表单。注意:这与Scheme不同,其中可以嵌套DEFUNs形式。由于未使用嵌套FLET,因此讨论效果毫无意义。转换示例以使用LABELSDEFUN定义的本地函数。

  • 当我在另一个defun中进行defun时,即使在let块中,为什么这个函数可以全局访问?

因为DEFUN定义了全局函数。这就是它的目的。

您需要重写示例以便讨论效果:

  • 声明所有变量

  • 不要使用嵌套的{{1}} s

我们可以尝试理解您当前示例中的代码,但很大程度上取决于实现和操作顺序。我们要么不在便携式Common Lisp领域,要么做(嵌套DEFUN),这在Common Lisp中没有实际用途。

答案 1 :(得分:0)

正如雷纳解释的那样,你的第一个问题的答案是defun定义了全局函数。使用标签来定义本地功能。

Rainer也非常依赖你的lisp配置。许多Lisps知道两种类型的范围,词法和动态。当您查看代码页以及您从大多数其他编程语言中习惯使用的内容时,您可以看到词法范围。当lisp在调用函数的环境中查找变量的值而不是在定义函数的位置查找变量的值时,就会获得动态范围。

最后,您的代码会使用闭包。当你在let中放置一个lambda(defun)时,你会创建一个闭包,例如你的变量&#34;挂起或浮动&#34;对于将来使用的函数,即使包含它们的函数返回给它的调用者。

所以可能正在进行中(正如Rainer说的那样,很难知道,因为你做的事情通常都没有完成,例如嵌套defuns):

所以...可能,test-scope1使用全局myvar,它的set-var引用了全局myvar,因为我们正在处理词法范围。 test-scope2构建一个闭包,它创建一个只能通过set-var2访问的词法范围的myvar。由于myvar具有词法范围,因此在test-scope2中调用set-var将不会使用此myvar,但仍然是全局的。

我不确定为什么调用test-scope3会得到102,因为test-scope3只调用set-var,它应该将全局myvar设置为2而不是102.唯一可以解释102的代码是set-var3,当你调用test-scope3时,不会调用它。如果在调用test-scope3时得到2而不是102,那么可能正在进行的操作:

(defun test-scope3 ()
  (let ((myvar (if (ignore-errors myvar)
                   myvar
                   100)))
    (defun set-var3 ()
      (setf myvar (+ myvar 100)))
    (set-var)))

使用let,您可以为具有词法范围的myvar创建一个本地绑定,但是您可以在其中引用全局myvar的绑定形式 - (if(ignore-errors myvar)myvar)。稍后你调用set-var,它应该再次引用全局myvar,因为test&#39; scope3&#39; s允许使用词法范围来绑定myvar。然后,你第一次调用set-var3,它接受上面let的myvar,它当前的值为2,并为它增加100。当你第二次调用它时,再次向词法绑定的myvar添加100,它仍然在你在test-scope3中构建的闭包中挂起,依此类推。但是当你再次调用test-scope3时,你会创建一个新的闭包(但是替换旧的闭包,因为你使用defun给它赋予相同的名字......),它再次将关闭的myvar的初始值设置为值全局myvar,即2。

我建议你阅读Paul Graham的优秀ANSI Common Lisp和On Lisp,它们解释了特殊变量(动态范围),词汇范围和深度闭包。

这里有几个闭包共享变量的方法:

(let ((myvar 0))
  (defun reset () 
    (setf myvar 0)
  (defun inc ()
    (setf myvar (+ myvar 1))))

这构建了两个闭包并使它们共享myvar,并且由于myvar具有词法范围,因此您无法从其他任何地方访问myvar的这个副本。因此,您基本上可以对myvar进行独占控制以重置和修改。

这可能是构建递归函数的一种方法,每次迭代都不需要传递给它的值。