lisp - 你能告诉我如何用 lisp 重写函数吗?

标签 lisp common-lisp

考虑这个 javascript:

function addX(n)
{
  return 3 + n;
}
alert(addX(6)); //alerts 9
eval('var newFunc = ' + addX.toString().replace("3", "5") + ';');
alert(newFunc(10)); //alert 15

请忽略它的用途和方法可疑、危险、难以在大型代码库中遵循等事实。它允许您根据用户的输入即时修改函数。我没有证明这一点,但我很容易就能做到。

我希望你能告诉我如何用 lisp 来做到这一点。看了很多教程,看了很多宏,asked a broader question , 尝试了很多东西,但最终都失败了。

我想知道如何在 lisp 中在运行时修改此函数以代替添加 5。或者用户可能输入的任何其他内容。

(define (addX n)
  (+ 3 n))

我不是在寻找柯里化(Currying)!我知道我可以做到这一点:

(define addXCurry 
  (lambda (x)
    (lambda (n)
      (+ x n))))

(define add5 (addXCurry 5))
(add5 10)

但这是在创建一个函数工厂。
我正在使用一个简单的示例,因为我想完全理解足够简单的事情的困难方法。


编辑 谢谢大家的回答。我想我对宏的最大困扰(据我所知)是我还没有看到一个将修改与编写完全分开的宏。 javascript 示例很简单 - 但您可以根据用户输入进行更复杂的重写。

我见过的宏都是基于“编译时”(我想是程序员编写时)。就像在 C++ 中一样,您不能拥有动态模板参数——它必须在编译时已知。

(看起来)在 lisp 中,您不能像在 javascript 中那样在运行时从根本上改变过程,因为您丢失了源代码。您可以评估并重新定义它,但您不能遍历列表的元素(列表是函数定义),检查每个元素并决定是否更改它。异常(exception)情况似乎是 Rainer 的回答中的例子,这些例子是摇摇欲坠的。

最佳答案

困难的部分是 Common Lisp(和其他一些 Lisp)摆脱了源代码。特别是当涉及到编译器时。默认情况下,源代码已经消失,剩下的就是机器代码。如何以何种形式恢复 Lisp 源代码?

背后的原因:为什么要要求 CL 程序保留源代码?它可以完全编译为机器代码或 C 代码,并且在运行时没有编译器/EVAL。该程序可以在没有太多开发环境(没有编译器等)的情况下运行。也不应要求 Common Lisp 环境能够将代码“反编译”为某种重构源代码。

而且它通常很复杂。

(let ((n 0) (step 2)) (defun foo () (incf n step)))

以上内容的来源是什么?您将如何更改 STEP?该功能取决于词法绑定(bind)。

另一个并发症:

(defun foo (n) (+ n #.(random 1.0)))

如何恢复?每次 Lisp 读取源文本时,都会读取一个随机数。

另一个并发症:

(setf (symbol-function 'foo) (compute-function))

您可以使用一些任意计算的函数或预定义函数(如 SIN)来设置函数值。如果它们被编译为机器码、加载为机器码等,如何恢复它们?

如果 Common Lisp 实现保留源代码,FUNCTION-LAMBDA-EXPRESSION 会检索它。

有两种解决方法:

a) 告诉 Lisp 源代码或记住源代码。

提供来源。

(let* ((fs (copy-list '(lambda (n) (+ n 3))))
   (fc (compile nil fs)))
   (print (funcall fc 6))
   (setf (third (third fs)) 5)
   (setf fc (compile nil fs))
   (funcall fc 6))

扩展示例:

写一个既能记住源代码又能定义函数的宏 DEFINE。

(defmacro define (&rest source)
  `(progn (setf (get ',(first source) :source) (list* 'defun ',source))
     (defun ,@source)))

上面将源代码放在 :SOURCE 下的符号属性列表中。

现在我们可以编写一个修改源代码并编译它的函数:

(defun modify (fname modifier)
  (let ((source (get fname :source)))
    (when source
      (setf (get fname :source) (funcall modifier source))
      (eval (get fname :source))
      (compile fname))))

示例定义:

(define addx (n) (+ n 3))

重写示例:

(modify 'addx (lambda (source) (setf (third (fourth source)) 6) source))

b) 一些 Common Lisp 实现实现了一个名为 FUNCTION-LAMBDA-EXPRESSION 的函数(在 ANSI Common Lisp 中定义)。

这个函数返回三个值:作为 Lisp 数据的源、closure-p 和名称。它允许您更改源代码、编译它并使用 COMPILE 将名称设置为新函数。代码示例留作练习。

问题:在 Common Lisp 中,宏 DEFUN 定义了函数。宏在幕后所做的事情(IDE 的簿记、代码重写等)取决于实现。因此,FUNCTION-LAMBDA-EXPRESSION 返回的代码(如果实现返回源代码)对于每个实现可能看起来不同。

这是一个 LispWorks 示例:

CL-USER 12 > (function-lambda-expression #'addx)

(LAMBDA (N)
  (DECLARE (SYSTEM::SOURCE-LEVEL #<EQ Hash Table{0} 217874D3>))
  (DECLARE (LAMBDA-NAME ADDX))
  (+ N 3))
NIL
ADDX

因此您可以操纵源表达式并更改它。

关于lisp - 你能告诉我如何用 lisp 重写函数吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1220149/

相关文章:

lisp - 使用 read-line 从 .txt 中读取,正在读取其他符号。记事本神器?

lisp - 关闭普通 lisp 中的结果打印

clojure - 为什么有些 lisp 将函数名放在函数定义的参数列表之外?

lisp - 在 Lisp 中一次定义 n 个函数

macros - 可以使用 destructuring-bind 定义 destructuring-setq 吗?

random - Common Lisp 中的(随机)不是那么随机吗?

javascript - 64位windows下运行jwacs(lisp程序)

lisp - 抑制 Lisp 中打印函数的输出

lisp - 在 Common Lisp 中,为什么 (if) 语句的多表达式主体需要 (progn)?

lisp - 如何在 LISP 中不递归两次