有没有办法在Lisp中声明局部变量(避免让)?

时间:2013-06-30 19:24:11

标签: lisp let

我喜欢Lisp,但我发现其中一件令人讨厌的事情就是它太过分了。

在命令式编程语言中,我可以通过使用中间值来打破长表达式,例如:

int x = someFunctionCall() ? someOtherFunctionCall() : 42;
int y = myUnterminableNameFunction(x);

而不是

int x = myUnterminableNameFunction(someFunctionCall() ? someOtherFunctionCall() : 42);

这也可以在Lisp中完成,但据我所知,只能使用letlet引入了额外的嵌套级别,我宁愿避免使用。

我不打算争论这个意见,而是想找到一种方法在单个非嵌套函数/宏调用中声明局部变量。类似下面的declare_local

(defun my_function (a b)
   (declare_local x (if (some_function_call) (some_other_function_call) 42))
   (my_unterminable_name_function x))

如果它不存在,它可以通过一个聪明的宏实现,而不会对性能有害吗?

4 个答案:

答案 0 :(得分:8)

您可以使用let*表单进行顺序绑定。

(let* ((x (if (some-function-call)
             (some-other-call)
             42))
       (y (my-unterminable-name-function x)))
   (bla-bla-bla)
   ...)

它确实嵌套,但不是那么多。

declare-local开始,它必须由一些外部宏处理。例如,您可以编写my-defun来检查其正文中的declare-local并对其进行转换。 更新但它有些反Lisp。 Lisp表单通常只影响嵌套表单。 cl:declare是我能想到的唯一例外。

答案 1 :(得分:5)

是的,在Common Lisp中使用&aux参数:

(defun foo (a b &aux x y z)
  (setq x ...)
  (setq y ...)
  .... )

或使用"prog feature"

(defun bar (a b)
  (prog (x y)
    (setq x a y b) 
    ....
    (return 42)
    ))

答案 2 :(得分:5)

不,在类似progn的身体中不会出现任何形式,并突然宣布新的变量,其范围超过progn的其余部分。

虽然这在某些方面会很方便(例如缩小和版本控制中只有较少的空白差异),但是有一个很大的缺点:编写分析代码的代码要困难得多。

Lisp的结构是这样的,当我们查看复合形式的最左边的符号时,我们知道它是什么,并且在该符号旁边有一些严格的,易于解析的语法,它告诉我们什么符号,如果有的话,介绍了该结构的范围。 (如果它做了这样的事情,它被称为绑定构造)。

可以在整个身体中散布变量定义的绑定构造需要额外的工作来查找所有这些位置。

如果你真的错过了其他语言的这个功能,你可以自己写一个宏来实现它。

这是一个可能的开始。

让我们调用宏(begin ...),并在(begin ...)内让我们支持(new (var [initform])*)形式的语法,它使用let语法引入一个或多个变量,除了它没有身体。这些变量的范围是begin形式的其余部分。

然后,任务是制作这种形式的宏变换语法:

(begin
  a b c
  (new (x 42))
  d e
  (new (y 'foo) (z))
  f g)

进入,例如,这段代码:

(progn
  a b c
  (let ((x 42))
    d e
    (let ((y 'foo) (z))
      f g)))

begin宏必须查看其所有参数形式,并将(new ...)个参数分开,并生成嵌套的let结构。

  

上述转换问题的结构暗示了一种简单的递归解决方案。

现代工业实力begin必须提供声明。也许我们可以允许(new ...)表单后面紧跟(declare ...)表单。然后根据模式((new A ...) (declare B ...) C ...) - &gt; (let (A ...) (declare B ...) C ...)折叠这两个模式,我们递归处理(C ...)以查找更多(new ...)。< / p>

当然,如果你有这个begin宏,你必须明确地使用它。没有任何简单的方法来重新定位具有“隐含预测”的现有Lisp构造,使得它们具有“隐式开始”。

当然,你总能做的是实现一个非常复杂的宏,如下所示:

(my-dialect-of-lisp
  ;; file full of code in your customized dialect of Lisp goes here
  )

my-dialect-of-lisp宏解析方言(即实现该方言的完整代码遍历)并将标准Lisp转换为标准Lisp。


附录:(begin ...)

的实施

(没有声明支持):

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defun begin-expander (forms)
    (if (null forms)
      nil
      (destructuring-bind (first &rest rest) forms
        (if (and (consp first)
                 (eq (first first) 'new))
          `((let (,@(rest first)) ,@(begin-expander rest)))
          `(,first ,@(begin-expander rest)))))))

(defmacro begin (&rest forms)
  (let ((expansion (begin-expander forms)))
    (cond
      ;; (begin) -> nil
      ((null expansion) nil)
      ;; (begin (new ...) ...) -> ((let (...) ...)) -> (let (...) ...)
      ((and (consp (first expansion))
            (eq (first (first expansion)) 'let))
        (first expansion))
      ;; (begin ...) -> (...) -> (progn ...)
      (t `(progn ,@expansion)))))

借助模式匹配库可以更好地表达这种情况。

答案 3 :(得分:3)

这是一个概念证明宏,它将平面列表中的变量声明提取为标准let*形式。

(defun my/vardecl-p (x)
  "Return true if X is a (VAR NAME VALUE) form."
  (and (listp x)
       (> (length x) 1)
       (eq 'var (car x))))

(defmacro my/defun (name args &rest body)
  "Special form of DEFUN with a flatter format for LET vars"
  (let ((vardecls (mapcar #'cdr
                          (remove-if-not #'my/vardecl-p body)))
        (realbody (remove-if #'my/vardecl-p body)))
    `(defun ,name ,args
       (let* ,vardecls
         ,@realbody))))

示例:

(my/defun foo (a b)
  (var x 2)
  (var y 3)
  (* x y a b))

(foo 4 5)
; => 120