Как получить имена аргументов, используя отражение - PullRequest
22 голосов
/ 07 марта 2009

Я хотел бы сделать несколько довольно тяжелых отражений в Ruby. Я хочу создать функцию, которая будет возвращать имена аргументов различных вызывающих функций выше стека вызовов (достаточно было бы только одного более высокого уровня, но зачем останавливаться на этом?). Я мог бы использовать Kernel.caller, перейти к файлу и разобрать список аргументов, но это было бы уродливо и ненадежно.

Функция, которая мне нужна, будет работать следующим образом:

module A
  def method1( tuti, fruity) 
    foo
  end
  def method2(bim, bam, boom)  
    foo
  end
  def foo
    print caller_args[1].join(",") #the "1" mean one step up the call stack
  end

end

A.method1
 #prints "tuti,fruity"
A.method2
 #prints "bim, bam, boom"

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

Я вижу, что мне, вероятно, понадобится какое-то расширение Си. Я полагаю, это означает, что мой вопрос в том, какая комбинация расширения C будет работать наиболее легко. Я не думаю, что Caller + ParseTree было бы достаточно сами по себе.

Что касается того, почему я хотел бы сделать это, вместо того, чтобы говорить «автоматическая отладка», возможно, я должен сказать, что я хотел бы использовать эту функциональность для автоматической проверки условий вызова и возврата функций:

def add x, y 
  check_positive
  return x + y
end

Где check_positive выдаст исключение, если x и y не будут положительными. Очевидно, что это будет нечто большее, но, надеюсь, это даст достаточно мотивации.

Ответы [ 5 ]

39 голосов
/ 16 марта 2010

В Ruby 1.9.2 вы можете тривиально получить список параметров любого Proc (и, следовательно, конечно же, любого Method или UnboundMethod) с помощью Proc#parameters:

A.instance_method(:method1).parameters # => [[:req, :tuti], [:req, :fruity]]

Формат представляет собой массив пар символов: тип (обязательный, необязательный, остаток, блок) и имя.

Для нужного формата попробуйте

A.instance_method(:method1).parameters.map(&:last).map(&:to_s)
  # => ['tuti', 'fruity']

Конечно, это все еще не дает вам доступ к вызывающей стороне.

15 голосов
/ 09 марта 2009

Предлагаю вам взглянуть на библиотеку action-args Мерб.

require 'rubygems'
require 'merb'

include GetArgs

def foo(bar, zed=42)
end

method(:foo).get_args # => [[[:bar], [:zed, 42]], [:zed]]

Если вы не хотите зависеть от Merb, вы можете выбрать и выбрать лучшие детали из исходного кода в github .

4 голосов
/ 07 марта 2009

У меня есть метод, который довольно дорогой и только почти работает.

 $shadow_stack = []

 set_trace_func( lambda {
   |event, file, line, id, binding, classname|
   if event == "call"
     $shadow_stack.push( eval("local_variables", binding) )
   elsif event == "return"
     $shadow_stack.pop
   end
 } )

 def method1( tuti, fruity )
   foo
 end
 def method2(bim, bam, boom)
   foo
   x = 10
   y = 3
 end

 def foo
   puts $shadow_stack[-2].join(", ")
 end

 method1(1,2)
 method2(3,4,4)

Выходы

 tuti, fruity
 bim, bam, boom, x, y

Мне любопытно, почему вы хотите использовать такую ​​функциональность таким обобщенным образом.

Мне любопытно, как вы думаете, эта функциональность позволит автоматическую отладку? Вам по-прежнему нужно вводить вызовы для вашей функции "foo". Фактически, что-то, основанное на set_trace_func, в большей степени может быть автоматическим, так как вам не нужно трогать существующий код. Действительно, именно так и реализован debug.rb в терминах set_trace_func.

Решения вашего точного вопроса действительно в основном, как вы обрисовали. используйте caller + parsetree или откройте файл и получите данные таким образом. Я не знаю возможности отражения, которая позволила бы вам получить имена аргументов. Вы можете одобрить моё решение, взяв соответствующий объект метода и вызвав #arity, чтобы затем определить, какие из local_variables являются аргументами, но, хотя кажется, что результат этой функции упорядочен, я не уверен, что безопасно полагаться на что. Если вы не возражаете, я спрашиваю, когда у вас есть данные и интерфейс, который вы описываете, что вы собираетесь с ними делать? Автоматическая отладка не была тем, что изначально приходило на ум, когда я предполагал использовать эту функциональность, хотя, возможно, это не с моей стороны.


Aha!

Тогда я бы подошел к этому иначе. Уже есть несколько библиотек ruby ​​для проектирования по контракту, включая ruby-contract, rdbc и т. Д.

Другой вариант - написать что-то вроде:

 def positive
   lambda { |x| x >= 0 }
 end

 def any
   lambda { |x| true }
 end

 class Module
   def define_checked_method(name, *checkers, &body)
     define_method(name) do |*args|
       unless checkers.zip(args).all? { |check, arg| check[arg] }
         raise "bad argument"
       end
       body.call(*args)
     end
   end
 end

 class A
   define_checked_method(:add, positive, any) do |x, y|
     x + y
   end
 end

 a = A.new
 p a.add(3, 2)
 p a.add(3, -1)
 p a.add(-4, 2)

Выходы

 5
 2
 checked_rb.rb:13:in `add': bad argument (RuntimeError)
         from checked_rb.rb:29

Конечно, это может быть сделано намного более изощренно, и, действительно, это то, что предоставили библиотеки, о которых я упоминал, но, возможно, это способ привести вас туда, куда вы хотите идти, не обязательно выбирая путь, который вы запланировали использовать для получения есть

1 голос
/ 18 марта 2010

, если вам нужно значение для значений по умолчанию, есть гем "arguments"

$ gem install rdp-arguments
$ irb
>> require 'arguments'
>> require 'test.rb' # class A is defined here
>> Arguments.names(A, :go)
0 голосов
/ 08 марта 2009

На самом деле описанный вами метод явно не может отличить аргументы от локальных переменных, а также не работает автоматически

Это потому, что вы пытаетесь не поддерживать. Это возможно (все возможно в ruby), но нет документированного или известного способа сделать это.

Либо вы можете проверить обратную трассировку, как предлагал логан, либо вы можете запустить свой компилятор C и взломать исходный код для ruby. Я достаточно уверен, что нет других способов сделать это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...