Как маршалировать лямбду (Proc) в Ruby?

Джо Ван Дайк спросил список рассылки Ruby:

Hi,

In Ruby, I guess you can't marshal a lambda/proc object, right? Is that possible in lisp or other languages?

What I was trying to do:

l = lamda { ... }
Bj.submit "/path/to/ruby/program", :stdin => Marshal.dump(l)

So, I'm sending BackgroundJob a lambda object, which contains the context/code for what to do. But, guess that wasn't possible. I ended up marshaling a normal ruby object that contained instructions for what to do after the program ran.

Joe

Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Пошаговое руководство по созданию собственного Slackbot: От установки до развертывания
Шаг 1: Создание приложения Slack Чтобы создать Slackbot, вам необходимо создать приложение Slack. Войдите в свою учетную запись Slack и перейдите на...
21
0
7 946
7
Перейти к ответу Данный вопрос помечен как решенный

Ответы 7

Попробуйте ruby2ruby

Ответ принят как подходящий

Вы не можете маршалировать Lambda или Proc. Это связано с тем, что оба они считаются замыканиями, что означает, что они закрываются вокруг памяти, в которой они были определены, и могут ссылаться на нее. (Чтобы их упорядочить, вам нужно будет маршалировать всю память, к которой они могли получить доступ в момент создания.)

Однако, как указал Гай, вы можете использовать ruby2ruby, чтобы получить строку программы. То есть вы можете упорядочить строку, представляющую код Ruby, а затем повторно оценить ее позже.

ruby2ruby работает только на 1.8, официального способа десериализации байт-кода 1.9 пока нет.

manveru 18.11.2009 12:00

Ruby2ruby некоторое время работает над MRI 1.9. Ripper тоже классный и идет с МРТ (с 1.9).

Wojciech Kaczmarek 03.09.2013 15:36

Если вы заинтересованы в получении строковой версии кода Ruby с помощью Ruby2Ruby, вам может понравиться эта ветка.

вы также можете просто ввести свой код в виде строки:

code = %{
    lambda {"hello ruby code".split(" ").each{|e| puts e + "!"}}
}

затем выполните его с помощью eval

eval code

который вернет рубиновую ламду.

использование формата %{} экранирует строку, но закрывает только несоответствующую фигурную скобку. то есть вы можете вставлять скобки, такие как %{ [] {} }, и они все еще закрыты.

большинство выделителей текстового синтаксиса не понимают, что это строка, поэтому по-прежнему отображают обычную подсветку кода.

Я нашел, что proc_to_ast лучше всего справляется с задачей: https://github.com/joker1007/proc_to_ast.

Точно работает в ruby ​​2+, и я создал PR для совместимости с ruby ​​1.9.3+ (https://github.com/joker1007/proc_to_ast/pull/3)

Это интересное решение, хотя, похоже, оно требует, чтобы исходный код процедуры присутствовал на диске. Это имеет смысл; было бы нетривиально (возможно, невозможно) декомпилировать байт-код YARV обратно в AST. YARV отбрасывает исходный AST, когда он больше не нужен, но пользовательский компилятор байт-кода может сохранить исходный AST, что позволяет этому методу также работать с динамически генерируемыми процедурами (т.е. созданными с помощью eval).

Paul Brannan 16.09.2018 13:17

Если proc определен в файле, U может получить местоположение файла proc, затем сериализовать его, а затем после десериализации использовать местоположение, чтобы снова вернуться в процесс.

proc_location_array = proc.source_location

после десериализации:

file_name = proc_location_array[0]

line_number = proc_location_array[1]

proc_line_code = IO.readlines(file_name)[line_number - 1]

proc_hash_string = proc_line_code[proc_line_code.index("{")..proc_line_code.length]

proc = eval("lambda #{proc_hash_string}")

Когда-то это было возможно с помощью внутреннего рубина (https://github.com/cout/ruby-internal), например:

p = proc { 1 + 1 }    #=> #<Proc>
s = Marshal.dump(p)   #=> #<String>
u = Marshal.load(s)   #=> #<UnboundProc>
p2 = u.bind(binding)  #=> #<Proc>
p2.call()             #=> 2

Есть некоторые предостережения, но прошло много лет, и я не могу вспомнить детали. Например, я не уверен, что произойдет, если переменная является динварой в привязке, в которую она выгружается, и локальной в привязке, где она повторно привязана. Сериализация AST (на MRI) или байт-кода (на YARV) нетривиальна.

Приведенный выше код работает с YARV (до 1.9.3) и MRI (до 1.8.7). Нет причин, по которым его нельзя заставить работать на Ruby 2.x с небольшим усилием.

Другие вопросы по теме