你能告诉我如何在lisp中重写函数吗?

Tom*_*ter 7 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
Run Code Online (Sandbox Code Playgroud)

请忽略这样一个事实:它使用和方法可疑,危险,难以在大型代码库中使用,等等.它允许您根据用户的输入动态修改功能.我没有证明,但我也很容易.

我希望你能告诉我如何在lisp中做到这一点.我已经阅读了很多教程,阅读了很多关于宏的内容,提出了一个更广泛的问题,尝试了很多东西,但最终还是做得很短.

我想知道,在lisp中,我可以在运行时修改此函数,而不是添加5.或者用户可能输入的任何其他内容.

(define (addX n)
  (+ 3 n))
Run Code Online (Sandbox Code Playgroud)

我不是在寻找currying!我知道我可以这样做:

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

(define add5 (addXCurry 5))
(add5 10)
Run Code Online (Sandbox Code Playgroud)

但这是在创建一个功能工厂.
我正在使用一个简单的例子,因为我想完全理解一些简单的东西.


编辑 谢谢大家的答案.我想我围绕宏的大笨蛋(据我所知),是因为我没有看到一个完全将修改与写作分开的东西.javascript示例很简单 - 但您可以根据用户输入执行更复杂的重写.

我见过的宏都是基于"编译时"(或者我编写的程序员编写的时间).就像在C++中一样,您不能拥有动态模板参数 - 必须在编译时知道它.

(看起来)在lisp中,你无法在运行时根据javascript中的方式从根本上改变过程,因为你丢失了源代码.您可以评估并重新定义它,但不能遍历列表的元素(列表是函数定义),检查每个元素并决定是否更改它.例外情况似乎是Rainer答案中的例子,这些例子都是不稳定的.

Rai*_*wig 16

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

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

它通常也很复杂.

(let ((n 0) (step 2)) (defun foo () (incf n step)))
Run Code Online (Sandbox Code Playgroud)

以上是什么来源以及如何改变STEP?该功能取决于词法绑定.

另一个复杂因素:

(defun foo (n) (+ n #.(random 1.0)))
Run Code Online (Sandbox Code Playgroud)

如何恢复?每当Lisp读取源文本时,将读取随机数.

另一个复杂因素:

(setf (symbol-function 'foo) (compute-function))
Run Code Online (Sandbox Code Playgroud)

您可以使用某个任意计算的函数或预定义的函数(如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))
Run Code Online (Sandbox Code Playgroud)

扩展示例:

写一个宏DEFINE,它记住源并定义函数.

(defmacro define (&rest source)
  `(progn (setf (get ',(first source) :source) (list* 'defun ',source))
     (defun ,@source)))
Run Code Online (Sandbox Code Playgroud)

上面将源代码放在符号属性列表下: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))))
Run Code Online (Sandbox Code Playgroud)

示例定义:

(define addx (n) (+ n 3))
Run Code Online (Sandbox Code Playgroud)

重写示例:

(modify 'addx (lambda (source) (setf (third (fourth source)) 6) source))
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

因此,您可以操纵源表达式并进行更改.


Les*_*zer 7

例外情况似乎是Rainer答案中的例子,这些例子都是不稳定的.

为什么?这是合乎逻辑的结论.您不能依赖编译器保留源代码,因此您只需自己存储它.

之后,您可以正确使用函数的定义(而不是Javascript,你只是破解字符串表示,这是一个摇摇欲坠的事情的一个主要的例子).