gpt4 book ai didi

macros - Common Lisp 宏的 Catch-22 情况

转载 作者:行者123 更新时间:2023-12-04 10:47:43 26 4
gpt4 key购买 nike

通常,当我尝试编写宏时,会遇到以下困难:我需要一个传递给宏的表单,然后在由生成宏扩展时调用的辅助函数处理之前对其进行评估。在下面的例子中,我们只对如何编写宏来发出我们想要的代码感兴趣,而不是宏本身的无用性:

想象一下(忍受我)Common Lisp 的一个版本 lambda宏,其中只有参数的数量很重要,参数的名称和顺序不重要。我们叫它jlambda .它将像这样使用:

(jlambda 2
...body)

哪里 2是返回的函数的元数。换句话说,这会产生一个二元运算符。

现在想象一下,给定元数, jlambda产生一个虚拟的 lambda 列表,它传递给实际的 lambda宏,是这样的:
(defun build-lambda-list (arity)
(assert (alexandria:non-negative-integer-p arity))
(loop for x below arity collect (gensym)))

(build-lambda-list 2)
==> (#:G15 #:G16)

以上电话扩展为 jlambda看起来像这样:
(lambda (#:G15 #:16)
(declare (ignore #:G15 #:16))
…body))

假设我们需要 jlambda宏能够将 arity 值作为 Lisp 形式接收,该形式计算为非负整数(与直接接收非负整数相反),例如:
(jlambda (+ 1 1)
...body)

表格 (+ 1 1)需要求值,那么结果需要传递给 build-lambda-list并且需要对其进行评估,并将其结果插入到宏扩展中。
(+ 1 1)
=> 2
(build-lambda-list 2)
=> (#:G17 #:18)

(jlambda (+ 1 1) ...body)
=> (lambda (#:G19 #:20)
(declare (ignore #:G19 #:20))
…body))

所以这是 jlambda的一个版本当 arity 直接作为数字提供时有效,但当它作为要评估的形式传递时则无效:
(defun jlambda-helper (arity)
(let ((dummy-args (build-lambda-list arity)))
`(lambda ,dummy-args
(declare (ignore ,@dummy-args))
body)))

(defmacro jlambda (arity &body body)
(subst (car body) 'body (jlambda-helper arity)))

(jlambda 2 (print “hello”)) ==> #<anonymous-function>

(funcall *
'ignored-but-required-argument-a
'ignored-but-required-argument-b)
==> “hello”
“hello”

(jlambda (+ 1 1) (print “hello”)) ==> failed assertion in build-lambda-list, since it receives (+ 1 1) not 2

我可以评估 (+ 1 1)使用尖点读取宏,如下所示:
(jlambda #.(+ 1 1) (print “hello”)) ==> #<anonymous-function>

但是表单不能包含对词法变量的引用,因为它们在读取时评估时不可用:
(let ((x 1))
;; Do other stuff with x, then:
(jlambda #.(+ x 1) (print “hello”))) ==> failure – variable x not bound

我可以引用我传递给 jlambda 的所有正文代码,改为将其定义为函数,然后 eval它返回的代码:
(defun jlambda (arity &rest body)
(let ((dummy-args (build-lambda-list arity)))
`(lambda ,dummy-args
(declare (ignore ,@dummy-args))
,@body)))

(eval (jlambda (+ 1 1) `(print “hello”))) ==> #<anonymous-function>

但是我不能用 eval因为,就像sharp-dot一样,它抛出了词法环境,这是不好的。

所以 jlambda必须是一个宏,因为我不希望函数体代码在 jlambda 为其建立适当的上下文之前进行评估。的扩展;但是它也必须是一个函数,因为我希望在将第一个形式(在本例中,arity 形式)传递给生成宏扩展的辅助函数之前对其进行评估。我如何克服这种 Catch-22 情况?

编辑

针对@Sylwester 的问题,以下是对上下文的解释:

我写的东西类似于 “esoteric programming language” ,在 Common Lisp 中实现为 DSL。这个想法(诚然愚蠢但可能很有趣)是强制程序员尽可能地(我不确定有多远!),专门在 point-free style 中编写。 .为此,我将做几件事:
  • 使用 curry-compose-reader-macros提供在 CL 中以无点样式编写所需的大部分功能
  • 强制函数的数量——即覆盖 CL 的默认行为,允许函数是可变参数
  • 无需使用类型系统来确定函数何时“完全应用”(如在 Haskell 中),只需在定义函数时手动指定函数的元数。

  • 所以我需要一个自定义版本的 lambda用这种愚蠢的语言定义一个函数,如果我想不通的话,还有一个自定义版本的 funcall和/或 apply用于调用这些函数。理想情况下,它们只是普通 CL 版本的外观,稍微改变了功能。

    这种语言中的函数必须以某种方式跟踪它的数量。然而,为了简单起见,我希望过程本身仍然是一个可调用的 CL 对象,但真的希望避免使用 MetaObject 协议(protocol),因为它比宏更让我困惑。

    一个潜在的简单解决方案是使用闭包。每个函数都可以简单地关闭存储其元数的变量的绑定(bind)。调用时,arity 值将确定函数应用程序的确切性质(即完整或部分应用程序)。如有必要,关闭可以是“pandoric”,以便提供对 arity 值的外部访问;可以使用 plambda 实现和 with-pandoric来自 Let Over Lambda .

    一般来说,我的语言中的函数会像这样(可能有错误的伪代码,纯粹是说明性的):
    Let n be the number of arguments provided upon invocation of the function f of arity a.
    If a = 0 and n != a, throw a “too many arguments” error;
    Else if a != 0 and 0 < n < a, partially apply f to create a function g, whose arity is equal to a – n;
    Else if n > a, throw a “too many arguments” error;
    Else if n = a, fully apply the function to the arguments (or lack thereof).

    事实证明 g等于 a – njlambda 的问题所在会出现: g需要像这样创建:
    (jlambda (- a n)
    ...body)

    这意味着访问词汇环境是必要的。

    最佳答案

    这是一个特别棘手的情况,因为没有明显的方法可以在运行时创建具有特定数量参数的函数。如果没有办法做到这一点,那么编写一个接受一个参数和另一个函数的函数可能是最简单的,并将该函数包装在一个需要提供特定数量参数的新函数中:

    (defun %jlambda (n function)
    "Returns a function that accepts only N argument that calls the
    provided FUNCTION with 0 arguments."
    (lambda (&rest args)
    (unless (eql n (length args))
    (error "Wrong number of arguments."))
    (funcall function)))

    一旦你有了它,就可以很容易地围绕它编写宏,你希望能够:
    (defmacro jlambda (n &body body)
    "Produces a function that takes exactly N arguments and and evalutes
    the BODY."
    `(%jlambda ,n (lambda () ,@body)))

    它的行为大致与您希望的方式相同,包括让 arity 成为编译时未知的东西。
    CL-USER> (let ((a 10) (n 7))
    (funcall (jlambda (- a n)
    (print 'hello))
    1 2 3))

    HELLO
    HELLO
    CL-USER> (let ((a 10) (n 7))
    (funcall (jlambda (- a n)
    (print 'hello))
    1 2))
    ; Evaluation aborted on #<SIMPLE-ERROR "Wrong number of arguments." {1004B95E63}>.

    现在,您可以使用 在运行时执行一些可能间接调用编译器的操作。强制 ,但这不会让函数体能够引用原始词法范围内的变量,尽管您会得到实现的错误数量的参数异常:
    (defun %jlambda (n function)
    (let ((arglist (loop for i below n collect (make-symbol (format nil "$~a" i)))))
    (coerce `(lambda ,arglist
    (declare (ignore ,@arglist))
    (funcall ,function))
    'function)))

    (defmacro jlambda (n &body body)
    `(%jlambda ,n (lambda () ,@body)))

    这适用于 SBCL:
    CL-USER> (let ((a 10) (n 7))
    (funcall (jlambda (- a n)
    (print 'hello))
    1 2 3))
    HELLO

    CL-USER> (let ((a 10) (n 7))
    (funcall (jlambda (- a n)
    (print 'hello))
    1 2))
    ; Evaluation aborted on #<SB-INT:SIMPLE-PROGRAM-ERROR "invalid number of arguments: ~S" {1005259923}>.

    虽然这在 SBCL 中有效,但我不清楚它是否真的保证有效。我们正在使用 强制编译一个包含文字函数对象的函数。我不确定这是否是可移植的。

    关于macros - Common Lisp 宏的 Catch-22 情况,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39426889/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com