考虑这个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的方式从根本上改变过程,因为你丢失了源代码。您可以评估并重新定义它,但不能遍历列表的元素(列表是函数定义),检查每个元素并决定是否更改它。例外情况似乎是Rainer的答案中的例子,这些例子不稳定。
答案 0 :(得分:16)
困难的部分是Common Lisp(和其他一些Lisps)摆脱了源代码。特别是涉及编译器时。默认情况下,源代码已经消失,剩下的就是机器代码了。如何恢复Lisp源以及它的形状?
背后的原因:为什么要求CL程序保留源代码?它可以完全编译为机器代码或C代码,并且在运行时没有编译器/ EVAL。该程序可以在没有太多开发环境(没有编译器等)的情况下运行。 Common Lisp环境也不需要能够将代码“解编”为某种重构源代码。
这也很复杂。
(let ((n 0) (step 2)) (defun foo () (incf n step)))
上述内容是什么?您如何能够改变STEP?该功能取决于词法绑定。
另一个复杂因素:
(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)))
Above将源代码放在符号属性列表下: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
所以你可以操纵源表达式并改变它。
答案 1 :(得分:7)
例外情况似乎就是例子 在Rainer的回答中,这是不稳定的 地面。
为什么呢?这是合乎逻辑的结论。您不能依赖编译器保留源代码,因此您只需自己存储它。
之后你可以正确使用函数的定义(而不是Javascript,你只是破解字符串表示,这是一个摇摇欲坠的事情的主要例子。)
答案 2 :(得分:3)
我们的程序代码:
(define add-x-code
'(lambda (n)
(+ 3 n)))
将其评估为函数:
(define add-x (eval add-x-code))
> (add-x 5)
8
改变它:
(define add-x-2 (eval (replace 3 7 add-x-code)))
> (add-x-2 5)
12
这与您在JavaScript代码中所做的类似。这是不是一个好主意不是你问的,所以我会把它留下来。
(简单的替换程序我掀起了:)
(define (replace x y list)
(map (lambda (x.)
(if (equal? x. x)
y
(if (list? x.)
(replace x y x.)
x.)))
list))
答案 3 :(得分:1)
你编写javascript的方式表明你正在寻找一个宏:
(define-syntax addX
(syntax-rules ()
((addX a) (+ 3 a))
((addX a b) (+ a b))))
所以这会给你:
> (addX 2)
5
> (addX 2 5)
7
宏给出了你想要的东西,因为它不是一个currying函数工厂,它不会重写宏的名称,你可以随同更改(虽然这可以用更复杂的东西来完成),但它确实可以为您提供所需的功能,它可以让您随时更改功能。