Functional programming 在Common Lisp中具有默认值的可选函数参数

Functional programming 在Common Lisp中具有默认值的可选函数参数,functional-programming,common-lisp,Functional Programming,Common Lisp,这是我正在编写的一个函数,它将根据开始值、结束值和下一个函数生成一个数字列表 (defun gen-nlist (start end &optional (next #'(lambda (x) (+ x 1)))) (labels ((gen (val lst) (if (> val end) lst (cons val (gen (next val) lst))))) (gen start '())

这是我正在编写的一个函数,它将根据开始值、结束值和下一个函数生成一个数字列表

(defun gen-nlist (start end &optional (next #'(lambda (x) (+ x 1))))
    (labels ((gen (val lst)
        (if (> val end) 
            lst
            (cons val (gen (next val) lst)))))
    (gen start '())))
但是,当将其输入SBCL repl时,我会收到以下警告:

; in: DEFUN GEN-NLIST
;     (SB-INT:NAMED-LAMBDA GEN-NLIST
;         (START END &OPTIONAL (NEXT #'(LAMBDA (X) (+ X 1))))
;       (BLOCK GEN-NLIST
;         (LABELS ((GEN #
;                    #))
;           (GEN START 'NIL))))
; 
; caught STYLE-WARNING:
;   The variable NEXT is defined but never used.

;     (NEXT VAL)
; 
; caught STYLE-WARNING:
;   undefined function: NEXT
; 
; compilation unit finished
;   Undefined function:
;     NEXT
;   caught 2 STYLE-WARNING conditions
不知何故,它确实看到了定义的下一个变量,但没有使用。 在我使用它的地方,比如在
(next val)
,它是一个未定义的函数

很明显我做错了什么。我就是不知道该怎么做。
这是用默认值指定可选函数参数的正确方法吗?

您已经用正确的方法定义了可选参数,但由于Common Lisp是一个Lisp-2,它区分了函数和变量值。可选函数作为变量提供,必须使用
funcall
调用

替换

(cons val (gen (next val) lst))

关于未使用变量的警告和关于未定义函数的警告都将消失


顺便说一句:您的默认函数可以替换为
#'1+

,如果有人需要查看完整的功能代码,多亏了上面的答案和注释,我的结论如下:

(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
    (labels ((gen (val)
        (if (funcall endp val end) 
            '()
            (cons val (gen (funcall next val))))))
      (gen start)))
这是尾部递归版本,在尝试创建大型列表时,不会出现函数调用堆栈溢出:

(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
    (labels ((gen (val lst)
        (if (funcall endp val end) 
            (reverse lst)
            (gen (funcall next val) (cons val lst)))))
      (gen start '())))
用法示例:

>
(gen nlist 0 10)

(01 2 3 4 6 7 8 9 10)

>
(gen nlist 0 10#'1+#'equal)

(01 2 3 4 5 6 7 8 9)

>
(gen-nlist 0-10#'1-#'
(gen-nlist-10 0)


(-10-9-8-7-6-5-4-3-2-10)

简单递归版本有一个主要问题:长列表的堆栈溢出。它作为学习练习很有用,但不适用于生产代码

典型的高效循环迭代如下所示:

(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
  (loop for i = start then (funcall next i)
        until (funcall endp i end)
        collect i))

它也应该是
(gen(funcall…)(cons…)
尾部递归才能工作。@jkiiski不,这会改变事情的顺序。这是一个完全不同的函数,必须以这种方式编写。虽然是的,但我编写的不是尾部递归。@λ-当然你也必须反转列表。现在,没有必要使用
LST
参数。如果你想发布一个/solution,您应该将其作为答案发布。不要将其作为编辑包含在问题中。问题是针对问题的。答案是针对答案的。有意义。已更改。如果您使用编辑器的自动缩进支持,代码的缩进将略有不同。最后一次调用位于标签内。不在同一级别。GEN func的主体我也这么认为,但出于某种原因,我的编辑器坚持以这种不正确的方式缩进。我将检查它的自动缩进绑定。我运行了上面的循环版本(L)、简单递归版本(SR)和尾部递归版本(TR)(根据我的回答,后两个版本),并针对不同的范围对每个元素进行计时。SR因调用堆栈不足而在100000个元素时失败。但L和TR都因堆空间不足而仅在100000000个元素时放弃。L似乎快了大约30%左右。这是为什么?L和TR都耗尽了堆,所以它们都必须以某种迭代过程为模式,对吗?那么a有什么不同呢关于循环,它比尾部递归更快?
(defun gen-nlist (start end &optional (next #'1+) (endp #'>))
  (loop for i = start then (funcall next i)
        until (funcall endp i end)
        collect i))