Common Lisp中lambda函数是否可返回自身?如何实现
实现自引用状态函数的方案
你想到的labels写法根本不是取巧,是Common Lisp里实现这类自引用闭包的标准、合规写法,完全没有hack成分。
你的核心问题本质是:匿名lambda在构造过程中,没有默认的名字可以指代自身,所以需要通过语言提供的绑定手段拿到自身引用,目前有两种成熟的直接实现方式:
方案1:使用labels局部函数绑定(推荐)
这是工程实践中最常用、可读性最好的写法,逻辑和你原本的代码几乎完全一致,只需要把匿名lambda改成labels定义的局部函数,就能在函数内部直接拿到自身引用:
(defun define-state (name acceptor) (labels ((state-func (context in-event) (multiple-value-bind (new-state? out-events new-context) (funcall acceptor context in-event) (values ;; 无新状态时直接返回当前函数自身 (if new-state? new-state? state-func) out-events new-context name)))) ;; 返回构造好的状态闭包 #'state-func))
为什么这不是取巧?
labels是Common Lisp标准定义的局部函数构造形式,它天生支持函数内部引用自身的绑定,就是专门用来处理局部递归、自引用闭包这类场景的- 这个绑定是完全闭包内部的,对外不可见,不会污染任何命名空间
- 没有额外的运行时开销,返回的
state-func就是你要的状态函数对象,状态不变时返回的就是对象本身,和你预期的逻辑完全匹配
方案2:使用读取宏构造循环引用(不推荐日常使用)
如果你就是不想写labels,可以用Common Lisp读取器的循环引用构造语法#n=/#n#,配合读取期求值#.直接在构造lambda字面量时绑定自身引用:
(defun define-state (name acceptor) #1='#.(lambda (context in-event) (multiple-value-bind (new-state? out-events new-context) (funcall acceptor context in-event) (values (if new-state? new-state? #1#) out-events new-context name))))
这种写法效果和labels方案完全一致,但可读性差,而且依赖读取器的行为,除了特殊场景外不推荐使用。
简单验证
你可以用一小段测试代码确认逻辑符合预期:
(let* ((end-state (define-state :end (lambda (ctx e) (values nil nil ctx)))) (start-state (define-state :start (lambda (ctx e) (if (eq e :go) (values end-state nil (cons :went ctx)) (values nil nil ctx)))))) ;; 传入非迁移事件,验证返回自身 (multiple-value-bind (next-state _ _ cur-name) (funcall start-state nil :stay) (format t "当前状态: ~a, 返回自身? ~a~%" cur-name (eq next-state start-state))) ;; 传入迁移事件,验证切换到新状态 (multiple-value-bind (next-state _ _ cur-name) (funcall start-state nil :go) (format t "当前状态: ~a, 迁移到终态? ~a~%" cur-name (eq next-state end-state))))
运行后会输出:
当前状态: START, 返回自身? T 当前状态: START, 迁移到终态? T
完全满足你用函数实现有限状态机的需求。
内容的提问来源于stack exchange,提问作者BitTickler
相关产品推荐
相关产品推荐

