【问题标题】:In what languages can you dynamically rewrite functions on the fly?您可以在哪些语言中动态地重写函数?
【发布时间】:2010-11-10 07:14:42
【问题描述】:

我最近有必要在 javascript 中动态地重写一个 javascript 函数。我做这件事的轻松程度和乐趣让我震惊。

这里我有一些 HTML:

<div id="excelExport1234" 
     onclick="if(somestuff) location.href='http://server/excelExport.aspx?id=56789&something=else'; else alert('not important');"
  >Click here to export to excel</div>

而且我无法更改输出的 HTML,但我需要向该链接添加一个额外的参数。我开始考虑它,并意识到我可以这样做:

excelExport = $('excelExport1234');
if (needParam)
        eval('excelExport.onclick = ' + excelExport.onclick.toString().replace("excelReport.aspx?id", "excelReport.aspx?extraParam=true&id") + ';');
else
        eval('excelExport.onclick = ' + excelExport.onclick.toString().replace("extraParam=true&", "") + ';');

它就像一个冠军! excelExport.onclick 返回一个函数对象,我将其转换为字符串,并对其进行一些字符串操作。由于它现在是“function() { ... }”的形式,我只是返回并将其分配给 dom 对象的 onclick 事件。不得不使用 eval 有点难看,但是 AFAIK 没有一个 javascript 函数构造函数可以接受一串代码并将其很好地转换为对象。

无论如何,我的意思不是我超级聪明(我不是),我的意思是这很酷。而且我知道 javascript 不是唯一可以做到这一点的语言。我听说 lisp 多年来一直为此目的使用宏。除了真正 grok 宏,你需要真正 grok lisp,而我不 grok,我只是'有点明白'。

所以我的问题是:您可以(轻松)用哪些其他语言动态重写函数,您能给我看一个简单的示例吗?我想看看您还可以在哪些地方做到这一点,以及如何完成了!

(另外,我不知道要标记什么,所以我随机猜测)

【问题讨论】:

  • 这就是疯狂。
  • 我从来没有说过这是最优雅的事情,但它非常酷。这就像大多数小技巧一样——危险的原因是它们可以依赖编译器实现等,但仍然聪明有趣。
  • 你并没有真正重写一个函数。您正在构建一个您评估的字符串。代码将函数分配给某个标识符。许多语言都有类似 eval 的东西。在运行时对函数进行查找称为“后期绑定”。在 Lisp 中,我们一直在做这些事情。大多数人不知道的:Javascript 基本上是一种具有不同语法的 Lisp 方言。最初设计者想要类似 Scheme 的东西,但不得不改变语法来取悦管理。

标签: programming-languages lisp dynamic-languages


【解决方案1】:

我以前在 TCL 一直这样做,轻而易举,效果很好。我可以通过网络调查一些东西的接口,然后动态创建一个定制的接口来访问和控制东西。例如,您可以从通用 SNMP 库创建自定义 SNMP 接口。

我没用过,但是 C# 有一些内置的支持来生成它自己的字节码,这相当令人印象深刻。

我在 C 中也做过这种事情,但它是不可移植的,几乎不值得麻烦。这是一种有时用于“自我优化”代码的技术,以生成适当的 C 函数以优化处理给定的数据集。

【讨论】:

  • 在 Tcl 中,您甚至可以重新定义内置控制结构,以获得终极的灵活性/疯狂(根据口味删除):-)
【解决方案2】:

LISP 是这方面的终极语言。 LISP 函数是实际的 LISP 列表,这意味着您可以像操作任何其他数据结构一样操作 LISP 源代码。

这里有一个非常简单的例子来说明它是如何工作的:

(define hi 
  (lambda () (display "Hello World\n")))
;; Displays Hello World
(hi)
(set! hi
      (lambda () (display "Hola World\n")))
;; Displays Hola World
(hi)

然而,这在函数是一等对象的任何语言中都是可能的。 LISP 的这种语法的强大功能最有趣的展示之一就是它的宏系统。我真的觉得我不能公正地讨论这个话题,所以如果你有兴趣,请阅读这些链接:

http://en.wikipedia.org/wiki/Macro_(computer_science)#Lisp_macros

http://cl-cookbook.sourceforge.net/macros.html

【讨论】:

  • Lisp 函数不是列表。定义函数的表达式是列表,但函数是不同类型的对象。
  • 将 Lisp 称为“LISP”已经是 20 世纪了。 :-)
【解决方案3】:

在 Perl 中很简单。

*some_func = sub($) {
    my $arg = shift;
    print $arg, "\n";
};
some_func('foo');

回复 Sam Saffron 的请求:

*hello_world = sub() {
    print "oops";
};
hello_world();
*hello_world = sub() {
    print "hello world";
};
hello_world();

【讨论】:

  • 混乱,你能用 Perl 重写我的 ruby​​ 示例吗? (见我的回答)
  • 我给了你一个 +1,但我的意思是长的 :)
【解决方案4】:

Self-modifying code 也称为退化代码。这通常被认为是一件坏事,并且它曾经是高级语言的目标,以防止它被轻松编写。

这是来自维基百科条目:

自修改代码被一些人视为一种不好的做法,它使代码更难阅读和维护。然而,在某些情况下,自我修改仍然被认为是可接受的,例如当子例程指针被动态更改时——即使效果几乎与直接修改相同。

【讨论】:

  • 我同意你在正常情况下绝对不应该这样做。多么可维护性的噩梦。但如前所述,它确实打开了一些非常简洁的大门——例如,有一些令人着迷的 CS 应用程序可以用于解决问题的代码。
  • 这个答案,不回答 OP 的问题。
【解决方案5】:

我想这取决于您对“轻松动态重写”的确切定义。例如,在 .Net 中,您有 Func 类型和 lambdas,它们允许您将函数定义为变量或临时匿名函数,例如。

int[] numbers = {1, 2, 3, 4, 5};

Func<int[], int> somefunc;
if (someCondition) 
{
   somefunc = (is => is.Sum());
} else {
   somefunc = (is => is.Count());
}

Console.WriteLine(somefunc(numbers).ToString());

上面是一个非常人为的例子,要么对整数数组中的项目进行计数,要么求和,然后使用动态创建的函数在某些任意条件下进行。

注意 - 请不要指出,这些事情可以在没有 lambdas 的情况下轻松完成(他们显然可以)我只是想写一个非常简单的例子来演示 C# 中的概念

【讨论】:

    【解决方案6】:

    我认为大多数动态语言都是如此。这是 Python 中的示例

    定义 f(x): 打印 x def new_function(x): print "hello", x f("世界") f = new_function f("世界")

    输出是

    世界 你好世界

    我认为应该谨慎使用这种技术

    【讨论】:

    • 这样的事情不会让 JIT 受挫吗?这对解释器来说很好,但对 JIT 来说不是很好,因为它们本质上是编译器,需要对此进行特殊处理
    【解决方案7】:

    方案允许您这样做。

    (define (salute-english name) (display "Hello ") (display name))
    (define (salute-french nom) (display "Salut ") (display nom))
    

    现在您通过将salute 变量分配给正确的函数(salute-englishsalute-french)来重新定义函数,如下所示:

    (define salute salute-english)
    
    (define (redefined-the-salute-function language)
      (if (eq? language 'french)
          (set! salute salute-french)
          (set! salute salute-english)))
    

    更一般的函数式编程语言允许您这样做,或者因为函数是一流的价值。函数可以被操纵、传递,有时也可以赋值给变量等等。然后列表包括:LispSchemeDylanOCamlSML。一些具有一流功能的语言包括 PythonRubySmalltalk,我认为是 Perl

    请注意,当您拥有一种可以交互式键入程序的交互式语言时,必须可以重新定义函数/方法:REPL 必须能够做到这一点,以防万一您碰巧重新键入了已经定义的函数。

    【讨论】:

      【解决方案8】:

      在 PLSQL 中:

      create or replace procedure test
      as
      begin
       execute immediate '
      create or replace procedure test2
      as
      begin
        null;
      end;
       ';
      end;
      /
      

      【讨论】:

        【解决方案9】:

        您可以在 C++ 中执行此操作,但这并不容易、不安全或不推荐。

        1. 生成源代码文本
        2. 调用编译器(fork & exec)来构建动态库。在 gcc 中,您可以在标准输入上传递要编译的源代码,而不必在文件中。
        3. 加载库(Windows 上的 LoadLibrary(),Linux 上的 dlopen())
        4. 获取指向任何您想要的函数的函数指针(Windows 上的 GetProcAddress(),Linux 上的 dlsym())
        5. 如果您想替换现有函数,如果它是一个虚函数,您可以修改 v-table 以指向新函数(这部分尤其是一个充满危险的可怕想法)。 v-table 的位置或它的格式不是 C++ 标准的一部分,但我使用的所有工具链本身都是一致的,所以一旦你弄清楚它们是如何做到的,它可能不会休息。

        【讨论】:

          【解决方案10】:

          这是 Python 中的其他内容(除了 luc 的答案),我不推荐,只是为了展示它 - 有 exec,它可以执行一个字符串,您可以将其构建为任何代码......

          此处显示的 I/O 来自 Python 2.5.2 解释器会话。只是一些从子字符串构造字符串以执行的简单示例(>>> 是解释器提示符)...

          >>> def_string = 'def my_func'
          >>> param_string_1 = '():'
          >>> param_string_2 = '(x):'
          >>> do_string_1 = '  print "Do whatever."'
          >>> do_string_2 = '  print "Do something with", x'
          >>> do_string_3 = '  print "Do whatever else."'
          >>> do_string_4 = '  print "Do something else with", x'
          >>> def_1 = '\n'.join([def_string+param_string_1, do_string_1, do_string_3])
          >>> print def_1
          def my_func():
            print "Do whatever."
            print "Do whatever else."
          >>> exec def_1
          >>> my_func()
          Do whatever.
          Do whatever else.
          >>> def_2 = '\n'.join([def_string+param_string_2, do_string_2, do_string_4])
          >>> print def_2
          def my_func(x):
            print "Do something with", x
            print "Do something else with", x
          >>> exec def_2
          >>> my_func('Tom Ritter')
          Do something with Tom Ritter
          Do something else with Tom Ritter
          >>> 
          

          【讨论】:

            【解决方案11】:

            Ruby 中的琐碎:

            def hello_world; puts "oops"; end
            hello_world
            # oops
            def hello_world; puts "hello world"; end
            hello_world
            # hello world
            

            当然,这个例子很无聊:

            require "benchmark"
            # why oh _why 
            class Object
              def metaclass; class << self; self; end; end
              def meta_eval &blk; metaclass.instance_eval &blk; end
            end
            
            class Turtle
            end
            
            def make_it_move(klass)
              klass.send(:define_method, :move) { |distance|
                puts "moving #{distance} meters"
                sleep(0.1 * distance)
              }
            end
            
            make_it_move(Turtle)
            
            turtle = Turtle.new
            turtle.move(1)
            # moving 1 meters
            
            def profile(instance, method)
              instance.meta_eval do
                m = instance_method(method)
                define_method method do |*a|
                  puts "Benchmarking #{instance.class} #{method}"
                  puts Benchmark.measure {
                    m.bind(instance).call(*a)
                  }
                end
              end
            end
            
            profile(turtle, :move)
            
            turtle.move(10)
            # Benchmarking Turtle move
            # moving 10 meters
            #  0.000000   0.000000   0.000000 (  1.000994)
            
            
            Turtle.new.move(3)
            # moving 3 meters 
            

            以上代码:

            1. 定义一个空白类
            2. 给它添加一个方法
            3. 抓取一个实例
            4. 在该实例上拦截该方法

            【讨论】:

              【解决方案12】:

              很多语言都支持更改函数的功能,这并不像您想象的那么复杂。在函数式语言中,函数是值,函数名是绑定到它们的符号,就像任何变量一样。如果语言允许您将符号重新分配给不同的函数,这很简单。

              我认为更有趣的功能是能够获取函数的源代码(上面的toString)和从字符串创建新函数(在本例中为eval)。

              【讨论】:

                猜你喜欢
                • 2010-10-09
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2010-10-13
                • 1970-01-01
                • 1970-01-01
                • 2014-05-09
                • 1970-01-01
                相关资源
                最近更新 更多