На каких языках вы можете динамически переписывать функции на лету? - PullRequest
5 голосов
/ 13 июля 2009

Недавно у меня возникла необходимость переписать функцию 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 () {...}", я просто возвращаюсь и назначаю его событию onclick объекта dom. Немного уродливо использовать eval, но на AFAIK нет конструктора функции javascript, который мог бы взять строку кода и красиво превратить ее в объект.

Во всяком случае, моя точка зрения не в том, что я супер умная (я не), моя точка зрения в том, что это круто . И я знаю, что JavaScript не единственный язык, который может это сделать. Я слышал, что у lisp были макросы в течение многих лет для этой конкретной цели. За исключением того, что вы действительно используете макросы, вам действительно нужно получать шутки, а я не шучу, я просто «вроде получаю».

Итак, мой вопрос: На каких других языках вы можете (легко) динамически переписывать функции, и можете ли вы показать мне простой пример? Я хочу посмотреть, где еще вы можете это сделать, и как это сделано!

(также я понятия не имею, что пометить, так как я взял случайные догадки)

Ответы [ 12 ]

9 голосов
/ 13 июля 2009

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

3 голосов
/ 13 июля 2009

Полагаю, это зависит от того, что именно вы определяете как «легко динамическое переписывание». Например, в .Net у вас есть тип Func и лямбда-выражения, которые позволяют вам определять функции как переменные или как временные анонимные функции, например.

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

Выше приведен очень надуманный пример подсчета элементов в массиве целых чисел или суммирования с использованием динамически созданных функций, подчиняющихся некоторым произвольным условиям.

Примечание - Пожалуйста, не указывайте, что эти вещи могут быть легко выполнены без лямбд (что они, очевидно, могут). Я просто пытался написать очень простой пример, чтобы продемонстрировать концепцию в C #

2 голосов
/ 13 июля 2009

Схема позволяет вам сделать это.

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

Теперь вы переопределяете функцию, назначая переменную salute правильной функции, либо salute-english, либо salute-french, например:

(define salute salute-english)

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

Более общий функциональный язык программирования позволяет вам делать это, или, поскольку функции имеют первостепенное значение. Функциями можно манипулировать, передавать, иногда назначать переменным и так далее. Список тогда включает: Lisp , Схема , Дилан , OCaml и SML . Некоторые языки, имеющие функции первого класса, включают Python , Ruby , Smalltalk , и я думаю Perl .

Обратите внимание, что когда у вас есть интерактивный язык, где вы можете интерактивно вводить вашу программу, переопределение функций / методов должно быть возможным: REPL должен быть в состоянии сделать это, на тот случай, если вы случайно наберете определение уже определенные функции.

2 голосов
/ 13 июля 2009

Я думаю, что это так в большинстве динамических языков. Вот пример на Python

def f(x):
    print x

def new_function(x): print "hello", x

f("world")    
f = new_function
f("world")

Выход

world
hello world

Я думаю, что такую ​​технику следует использовать осторожно

2 голосов
/ 13 июля 2009

Самомодифицирующийся код также называется вырожденным кодом. Это, как правило, считается плохой вещью, и раньше целью языков высокого уровня было предотвращение ее легкого написания.

Это из записи википедии:

Самоизменяющийся код рассматривается некоторыми как плохая практика, которая затрудняет чтение и сопровождение кода. Однако существуют способы, в которых самомодификация, тем не менее, считается приемлемой, например, когда указатели подпрограмм изменяются динамически, даже если эффект почти идентичен прямому изменению.

1 голос
/ 13 июля 2009

Вы можете сделать это в C ++, но это не будет легко, безопасно или не рекомендуется.

  1. Генерация текста исходного кода
  2. вызывает компилятор (fork & exec) для создания динамической библиотеки. В gcc вы можете передавать исходный код, который вы хотите скомпилировать, на стандартный ввод, он не обязательно должен быть в файле.
  3. Загрузка библиотеки (LoadLibrary () в Windows, dlopen () в Linux)
  4. получить указатель на функцию, которую вы хотите (GetProcAddress () в Windows, dlsym () в Linux)
  5. Если вы хотите заменить существующую функцию, если это виртуальная функция, вы можете изменить таблицу v, чтобы она указала на новую функцию (эта часть особенно ужасна, чревата опасностью). Расположение v-таблицы или ее формат не являются частью стандарта C ++, но все используемые мной цепочки инструментов были согласованы внутри самих себя, поэтому, как только вы выясните, как они это делают, вероятно, это не произойдет сломаться.
1 голос
/ 13 июля 2009

Достаточно просто в Perl.

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

Re запрос Сэма Шафрана:

*hello_world = sub() {
    print "oops";
};
hello_world();
*hello_world = sub() {
    print "hello world";
};
hello_world();
1 голос
/ 13 июля 2009

Раньше я делал это все время в TCL, это было легко и чудесно работало. Я мог бы исследовать некоторый интерфейс по сети, а затем создать индивидуальный интерфейс на лету, чтобы получить доступ и контролировать вещи. Например, вы можете создать собственный интерфейс SNMP из общей библиотеки SNMP.

Я не использовал его, но C # имеет некоторую встроенную поддержку для генерации своего собственного байт-кода, что довольно впечатляет.

Я делал подобные вещи и в C, но там это непереносимо и почти никогда не стоит хлопот. Это метод, используемый иногда для «самооптимизации» кода для генерации соответствующей функции C для оптимальной обработки данного набора данных.

0 голосов
/ 05 сентября 2009

Изменение действия функции поддерживается во многих языках, и это не так сложно, как вы думаете. В функциональных языках функции - это значения, а имена функций - это символы, которые связаны с ними как любая переменная. Если язык позволяет переназначить символ другой функции, это тривиально.

Я думаю, что более интересными являются возможность получить исходный код функции (toString выше) и создать новую функцию из строки (eval в данном случае).

0 голосов
/ 05 сентября 2009

Тривиально в 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. Перехватывает этот метод в этом экземпляре * только 1018 *
...