define-record-type字段声明中子宏无法展开的问题排查与解决
想要自定义一个记录类型宏,字段定义格式为:(<field-name> <field-getter> [ setter <field-setter> ] [ default <field-default> ])(default留作后续开发)。为适配可选的setter关键字,使用了子宏record-type-field-spec处理字段格式,但宏无法正常展开,调用时解释器报错。
原始代码
(import (scheme base)) (define-syntax record-type-field-spec (syntax-rules (setter default) ((_ name getter setter setter-name default default-value) (name getter setter-name)) ((_ name getter setter setter-name) (name getter setter-name)) ((_ name getter) (name getter)) ((_ name getter setter setter-name) (name getter setter-name)) ((_ name getter default default-value) (name getter)))) (define-syntax define-record-type-2 (syntax-rules () ((_ name (maker params ...) predicate (field-name field-getter field-other ...) ...) (define-record-type name (maker params ...) predicate (record-type-field-spec field-name field-getter field-other ...) ...)))) (define-record-type-2 Rec (make-Rec a b c d) Rec? (a Rec-a) (b Rec-b setter Rec-b!) (c Rec-c default #f) (d Rec-d setter Rec-d! default #f))
报错信息
Chibi解释器报错
ERROR: unknown record field in constructor a ((#<SC 140018294227808 record-type-field-spec #<procedure #f 1>> a Rec-a) (#<SC 140018294227808 record-type-field-spec #<procedure #f 1>> b Rec-b setter Rec-b!) (#<SC 140018294227808 record-type-field-spec #<procedure #f 1>> c Rec-c default #f) (#<SC 140018294227808 record-type-field-spec #<procedure #f 1>> d Rec-d setter Rec-d! default #f)) called from <anonymous> on line 1268 of file /gnu/store/g9dqsd7m9h60av70lih8q4kdf259scsl-chibi-scheme-0.11/share/chibi/init-7.scm called from <anonymous> on line 800 of file /gnu/store/g9dqsd7m9h60av70lih8q4kdf259scsl-chibi-scheme-0.11/share/chibi/init-7.scm
Gosh解释器报错
*** ERROR: invalid field spec: (#<identifier r7rs.user#record-type-field-spec.fcf59dc0> b Rec-b setter Rec-b!) While compiling "./test.scm" at line 34: (define-record-type-2 Rec (make-Rec a b c d) Rec? (a Rec-a) (b Rec-b setter Rec-b!) (c Rec-c default ... While loading "./test.scm" at line 41 Stack Trace: _______________________________________ 0 (proc (next)) 1 (add! (proc (next))) 2 ((macro-transformer mac) expr cenv) 3 (call-macro-expander gval program cenv) 4 (pass1 program cenv) 5 (eval s #f) 6 (with-error-handler (lambda (e) (cond (else (let1 e2 (if (con ... 7 (load-from-port (if ignore-coding port (open-coding-aware-por ...
直接调用define-record-type并传入record-type-field-spec调用时也会触发类似错误。
Scheme宏遵循外层先展开、内层后展开的顺序,但define-record-type是内置特殊宏,它要求字段参数是直接的语法结构(如(name getter)或(name getter setter)),不会主动展开参数内的宏调用。因此record-type-field-spec的调用未被展开就传递给了define-record-type,导致解释器无法识别非法的字段格式。
需要在外层宏define-record-type-2内部,先将每个字段展开为define-record-type可识别的格式,再传递给它。以下是两种可行实现:
方式1:将字段匹配逻辑整合到外层宏
直接在define-record-type-2的语法规则中处理所有字段格式,无需子宏:
(import (scheme base)) (define-syntax define-record-type-2 (syntax-rules (setter default) ;; 带setter和default的字段 ((_ name (maker params ...) predicate (field-name field-getter setter setter-name default default-val) rest-fields ...) (define-record-type-2 name (maker params ...) predicate (field-name field-getter setter setter-name) rest-fields ...)) ;; 仅带setter的字段 ((_ name (maker params ...) predicate (field-name field-getter setter setter-name) rest-fields ...) (define-record-type name (maker params ...) predicate (field-name field-getter setter-name) (define-record-type-2 name (maker params ...) predicate rest-fields ...))) ;; 仅带default的字段 ((_ name (maker params ...) predicate (field-name field-getter default default-val) rest-fields ...) (define-record-type-2 name (maker params ...) predicate (field-name field-getter) rest-fields ...)) ;; 仅基础字段 ((_ name (maker params ...) predicate (field-name field-getter) rest-fields ...) (define-record-type name (maker params ...) predicate (field-name field-getter) (define-record-type-2 name (maker params ...) predicate rest-fields ...))) ;; 终止条件:无剩余字段 ((_ name (maker params ...) predicate) (void)))) (define-record-type-2 Rec (make-Rec a b c d) Rec? (a Rec-a) (b Rec-b setter Rec-b!) (c Rec-c default #f) (d Rec-d setter Rec-d! default #f))
方式2:用let-syntax在宏内部展开子宏
在define-record-type-2内部定义子宏,确保字段先被处理再传递给define-record-type:
(import (scheme base)) (define-syntax define-record-type-2 (syntax-rules () ((_ name (maker params ...) predicate (field-spec ...)) (let-syntax ((process-field (syntax-rules (setter default) ((_ name getter setter setter-name default default-val) (name getter setter-name)) ((_ name getter setter setter-name) (name getter setter-name)) ((_ name getter default default-val) (name getter)) ((_ name getter) (name getter))))) (define-record-type name (maker params ...) predicate (process-field . field-spec) ...))))) (define-record-type-2 Rec (make-Rec a b c d) Rec? (a Rec-a) (b Rec-b setter Rec-b!) (c Rec-c default #f) (d Rec-d setter Rec-d! default #f))
两种方式都能正确将带setter/default的字段转换为define-record-type可识别的格式。方式2更贴近最初的子宏思路,通过let-syntax确保子宏在define-record-type展开前完成字段处理。
内容的提问来源于stack exchange,提问作者ChillPC

