Lisp中SEARCH函数:KEY用EVENP/ODDP的对比逻辑及规范查询
Common Lisp SEARCH函数示例与KEY参数行为解析
一、SEARCH示例的对比逻辑
先看规范中的示例:
(search '(0 1) '(2 4 6 1 3 5) :key #'oddp) => 2
这个表达式的执行逻辑分三步:
- 应用KEY函数转换元素:
:key #'oddp表示对两个序列的每个元素都调用oddp(判断奇数),得到转换后的序列:- 模式序列
(0 1)转换后:(nil t)(0是偶数,oddp返回nil;1是奇数,返回t) - 目标序列
(2 4 6 1 3 5)转换后:(nil nil nil t t t)(2、4、6是偶数→nil;1、3、5是奇数→t)
- 模式序列
- 匹配转换后的子序列:SEARCH用默认的
eql测试,在转换后的目标序列中查找第一个匹配转换后模式的子序列。转换后的目标序列中,从索引2开始的连续两个元素是(nil t),正好与转换后的模式完全匹配。 - 返回起始位置:匹配的子序列在原目标序列中的起始索引是2,所以最终返回2。
二、关于默认TEST与隐式转换的疑问
在SBCL中测试发现默认的eql无法直接对比nil和0,但这个示例不存在隐式转换,也不是示例自定义行为——因为整个对比过程是基于KEY函数转换后的结果,而非原元素。原元素0和1并没有直接参与对比,参与对比的是它们经过oddp转换后的广义布尔值nil和t,而eql可以正常对比nil和t,所以示例行为完全符合规范。
三、SEARCH与ASSOC的KEY行为差异
你提到的ASSOC与SEARCH的key参数行为差异,核心原因是两个函数对key的应用场景不同:
- SEARCH:对模式序列和目标序列的每个元素都应用
key函数,然后对比转换后的子序列是否匹配。 - ASSOC:仅对关联列表中每个关联项的car部分应用
key函数,然后将转换结果与第一个参数(要查找的目标值)用test函数对比。
看你的测试示例:
CL-USER> (assoc 1 '((2 . A) (4 . B) (5 . C)) :key #'oddp) NIL CL-USER> (assoc t '((2 . A) (4 . B) (5 . C)) :key #'oddp) (5 . C)
第一个表达式中,assoc对每个关联项的car(2、4、5)应用oddp,得到nil、nil、t,然后用eql对比目标值1和这三个结果,没有匹配项,所以返回nil;第二个表达式中,目标值是t,与5转换后的t匹配,所以返回(5 . C)。
四、序列函数KEY参数的规范定义
Common Lisp规范(CLHS)中,序列函数的:key参数行为统一定义在17.2.1 序列函数的关键字参数章节。该章节明确:
:key参数接收一个单参数函数,默认是#'identity(返回元素本身)。- 函数会被应用于序列的每个元素(或关联类函数的特定部分,如
assoc的关联项car),转换后的结果才会参与后续的测试、匹配逻辑。 - 不同序列函数的具体应用细节,会在各自的函数文档中补充说明(比如SEARCH需同时处理模式和目标序列,ASSOC仅处理关联项的car)。
内容的提问来源于stack exchange,提问作者Jérôme Radix
相关产品推荐
相关产品推荐

