阅读Python's range() analog in Common Lisp之后,我开始认为我并不喜欢答案中使用的功能界面。
出现了三个不同的lambda列表:
(start end &optional (step 1))
:start
和end
参数都是必需的。
(end &key (start 0) (step 1))
:恕我直言,使用关键字参数对于这样一个简单的函数来说似乎有点过头了,它们只是为了隐藏end
和start
没有出现在自然顺序(即第一个start
,然后是end
)
(n &key (start 0) (step 1))
(来自alexandria:iota
):这里,参数的可选性和顺序是正确的,但代价是使用不同的抽象。
问题是我想写(range 6)
来生成(0 1 2 3 4 5)
,还要(range 3 6)
来生成(3 4 5)
。实际上,它很容易实现;例如:
(defun range (start_or_end &optional end (step 1))
(multiple-value-bind (start end)
(if end
(values start_or_end end)
(values 0 start_or_end))
(loop for n from start below end by step collect n)))
但是,我还没有看到这种争论在其他代码中摆弄,而作为一个Lisp新手,我想知道这是否是一个可接受的成语。
答案 0 :(得分:4)
作为Alessio Stalla pointed out,这与此没有关系,但它不是你经常看到的东西。当语言允许可选和休息参数时,arity的重载变得更加复杂。
我认为通常会处理这种情况的方式是用指定符来定义事物。您可以声明范围由三个值确定:开始,结束和步骤。然后你可以说范围指示符是一个长度最多为3的列表,具有以下语义:
(n)
指定(:start 0 :end n :step 1)
(m n)
指定(:start m :end n :step 1)
(m n s)
指定(:start m :end n :step s)
然后你可以做类似的事情:
(defun range (&rest range-designator)
(destructuring-bind (a &optional (b nil bp) (c nil cp))
range-designator
(multiple-value-bind (start end step)
(cond
(cp (values a b c))
(bp (values a b 1))
(t (values 0 a 1)))
(loop for x from start to end by step
collect x))))
CL-USER> (range 5)
(0 1 2 3 4 5)
CL-USER> (range 2 7)
(2 3 4 5 6 7)
CL-USER> (range 2 7 3)
(2 5)
如果你期望在其他地方使用范围指示符,你可以将内部的东西拉出来:
(defun to-range (designator)
(destructuring-bind (a &optional (b nil bp) (c nil cp))
designator
(cond
(cp (values a b c))
(bp (values a b 1))
(t (values 0 a 1)))))
(defun range (&rest range-designator)
(multiple-value-bind (start end step)
(to-range range-designator)
(loop for x from start to end by step collect x)))
答案 1 :(得分:2)
虽然你不经常遇到它,但这是可以接受的。我很确定标准中有这样的签名功能,但我现在记不起了。
我记得的一个例子是ABCL中的JFIELD原语:http://abcl.org/trac/wiki/JavaFfi#FunctionJFIELDJFIELD-RAWSETFJFIELD
如果你担心性能,因为“解析”lambda列表有成本,你可以使用编译器宏来避免支付它,特别是在像你这样的情况下,函数的行为只由数字驱动参数(与其类型相对)。