You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否通过宏实现支持任意数量car/cdr调用的c[...]r组合?——兼论Scheme与Common Lisp的默认限制及setf兼容方案

Great question—this is a classic Lisp quirk that trips up a lot of folks when they first start pushing beyond the standard c...r combinations. Let's tackle each of your questions in detail:

1. Can we generate arbitrary c[...]r combinations with macros?

Absolutely! The cadaddr-style functions are just syntactic sugar for nested car and cdr calls. A macro can easily parse the sequence of as and ds in the function name, then expand into the corresponding nested calls.

Here’s a simple Common Lisp example that defines a macro to generate any c...r function:

(defmacro def-cr (name)
  (let ((chars (coerce (string name) 'list)))
    (unless (and (eq (first chars) #\c) (eq (last chars) #\r))
      (error "`~a` doesn't follow the c...r naming convention" name))
    (let ((ops (butlast (rest chars))))
      (labels ((build-form (remaining-ops)
                 (if (null remaining-ops)
                     'x
                     (case (first remaining-ops)
                       (#\a `(car ,(build-form (rest remaining-ops))))
                       (#\d `(cdr ,(build-form (rest remaining-ops))))))))
        `(defun ,name (x) ,(build-form ops))))))

;; Now define our long-form function
(def-cr cadaddr)
;; Test it: returns 5
(cadaddr (list 1 3 (list 5 7) 9))

In Scheme, you can achieve the same with define-syntax (R5RS and later):

(define-syntax cr-expander
  (syntax-rules (a d)
    ((_ x) x)
    ((_ a rest ...) (car (cr-expander rest ...)))
    ((_ d rest ...) (cdr (cr-expander rest ...)))))

(define-syntax define-cr
  (syntax-rules ()
    ((_ name)
     (define name
       (lambda (x)
         (cr-expander #,@(cdr (butlast (string->list (symbol->string 'name)))) x))))))

(define-cr cadaddr)
;; Test it: returns 5
(cadaddr (list 1 3 (list 5 7) 9))

2. Can we write a macro that supports arbitrary a/d sequences AND works with setf?

Yes, in Common Lisp—this just requires adding a defsetf extension alongside the accessor function. Here’s an updated version of the macro that handles both reading and writing:

(defmacro def-cr-with-setf (name)
  (let ((chars (coerce (string name) 'list)))
    (unless (and (eq (first chars) #\c) (eq (last chars) #\r))
      (error "`~a` doesn't follow the c...r naming convention" name))
    (let ((ops (butlast (rest chars))))
      (labels ((build-accessor (remaining-ops)
                 (if (null remaining-ops)
                     'x
                     (case (first remaining-ops)
                       (#\a `(car ,(build-accessor (rest remaining-ops))))
                       (#\d `(cdr ,(build-accessor (rest remaining-ops)))))
               (build-setter (remaining-ops value)
                 (if (null (rest remaining-ops))
                     (case (first remaining-ops)
                       (#\a `(setf (car x) ,value))
                       (#\d `(setf (cdr x) ,value)))
                     (case (first remaining-ops)
                       (#\a `(setf (car ,(build-accessor (rest remaining-ops))) ,value))
                       (#\d `(setf (cdr ,(build-accessor (rest remaining-ops))) ,value))))))
        `(progn
           (defun ,name (x) ,(build-accessor ops))
           (defsetf ,name (x) (value)
             ,(build-setter ops 'value)))))))

;; Test with setf
(def-cr-with-setf cadaddr)
(let ((lst (list 1 3 (list 5 7) 9)))
  (setf (cadaddr lst) 10)
  lst) ; Returns (1 3 (10 7) 9)

Scheme doesn’t have a built-in setf, but if you’re using a dialect with set! and mutable pairs, you can extend the macro to generate setter functions or use a syntax-case approach to handle assignment.

3. If this is possible, why don’t mainstream Lisps include it by default?

There are three key reasons:

  • Historical legacy: Early Lisp implementations pre-defined c...r functions only up to 4 letters because pre-compiled functions were faster than macro expansions in the days of limited computing power. ANSI Common Lisp formalized this limit, and Scheme followed to maintain compatibility with existing code and conventions.
  • Readability: Beyond 3-4 as and ds, c...r names become unreadable. Most programmers prefer explicit car/cdr nesting or functions like nth for clarity—(car (cdr (car (cdr (cdr x))))) is easier to parse at a glance than cadaddr.
  • Minimalism and standardization: Scheme’s core philosophy prioritizes minimalism, leaving extensions like this to user-defined macros. For Common Lisp, the ANSI standard doesn’t require supporting longer combinations, so implementations stick to the defined set to ensure portability across platforms. Since you can easily implement this yourself with a macro, there’s no need to bloat the standard library with it.

内容的提问来源于stack exchange,提问作者J. Mini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 05:27:48