代码之家  ›  专栏  ›  技术社区  ›  Tom Ritter

你能告诉我如何重写Lisp中的函数吗?

  •  7
  • Tom Ritter  · 技术社区  · 16 年前

    考虑一下这个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))
    

    我不想吃咖喱 !我知道我能做到:

    (define addXCurry 
      (lambda (x)
        (lambda (n)
          (+ x n))))
    
    (define add5 (addXCurry 5))
    (add5 10)
    

    但这是在创建一个功能工厂。
    我用的是一个简单的例子,因为我想完全理解简单的事情上的困难。


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

    我看到的宏都是基于“编译时”(或者我想是程序员编写的时间)的。像C++一样,你不能有一个动态模板参数——它必须在编译时知道。

    (看起来)在Lisp中,您不能像在javascript中那样在运行时从根本上改变一个过程,因为您丢失了源代码。您可以对它进行评估和重新定义,但不能遍历列表的元素(列表是函数定义),检查每个元素并决定是否更改它。例外情况似乎是雷纳答案中的例子,那是不稳定的基础。

    4 回复  |  直到 10 年前
        1
  •  16
  •   Rainer Joswig mmmmmm    16 年前

    最困难的部分是普通的lisp(和其他一些lisp)摆脱了源代码。尤其是当涉及编译器时。默认情况下,源代码不存在,只剩下机器代码。如何恢复Lisp源以及以什么形状?

    原因:为什么需要一个cl程序来保存源代码?它可以完全编译为机器代码或C代码,并且在运行时没有编译器/eval。程序可以在没有开发环境(没有编译器等)的情况下运行。通用的Lisp环境也不需要能够将代码“解压”成某种重构的源代码。

    而且,这通常是复杂的。

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

    以上内容的来源是什么?您将如何改变步骤?函数依赖于词汇绑定。

    另一个并发症:

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

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

    另一个并发症:

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

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

    如果公共Lisp实现保留源代码,则函数lambda表达式将检索它。

    有两种方法可以解决这个问题:

    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))
    

    扩展示例:

    编写一个宏定义,它既记住源代码,又定义函数。

    (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)一些常见的lisp实现实现了一个名为function-lambda-expression的函数(在ansi common lisp中定义)。

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

    问题:在普通的lisp中,宏defun定义函数。宏在幕后所做的(IDE的簿记、代码重写等)取决于实现。因此,由函数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
    

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

        2
  •  7
  •   Leslie P. Polzer    16 年前

    例外似乎就是例子 在雷纳的回答中,这是不稳定的 地面。

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

    之后你可以 适当地 使用函数的定义(与Javascript相反,Javascript只是黑客一个字符串表示,这是一个不稳定事物的主要例子)。

        3
  •  3
  •   Christopher Done    16 年前

    我们的程序代码:

    (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))
    
        4
  •  1
  •   Pinochle    16 年前

    您编写JavaScript的方式表明您正在寻找一个宏:

    (define-syntax addX
      (syntax-rules ()
        ((addX a) (+ 3 a))
        ((addX a b) (+ a b))))
    

    所以这给了你:

    > (addX 2)
    5
    > (addX 2 5)
    7
    

    宏提供了你想要的东西,因为它不是一个当前的函数工厂,它不会重写宏的名称以便你进行更改(虽然这可以用更复杂的东西来完成),但它提供了你想要的东西,因为它允许你动态地更改函数。