Подтвердить что ты не робот

Как получить имена аргументов, используя отражение

Я хотел бы сделать довольно серьезное отражение в 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. Полагаю, это означает, что мой вопрос заключается в том, какая комбинация расширения C будет работать наиболее легко. Я не думаю, что Caller + ParseTree будет достаточно.

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

def add x, y 
  check_positive
  return x + y
end

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

4b9b3361

Ответ 1

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

require 'rubygems'
require 'merb'

include GetArgs

def foo(bar, zed=42)
end

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

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

Ответ 2

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

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

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

В нужном формате попробуйте

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

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

Ответ 3

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

 $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 являются аргументами, но, хотя он появляется, результат этой функции упорядочен, я не уверен, что безопасно полагаться на том. Если вы не возражаете, я спрашиваю, как только у вас будут данные и интерфейс, которые вы описываете, что вы собираетесь с ним делать? Автоматическая отладка не была тем, что первоначально приходило в голову, когда я предполагал использовать эту функциональность, хотя, возможно, с моей стороны не получается вообразить.


Ага!

Тогда я поступил бы иначе. Есть несколько рубиновых библиотек для разработки дизайна по контракту, включая 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

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

Ответ 4

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

$ gem install rdp-arguments
$ irb
>> require 'arguments'
>> require 'test.rb' # class A is defined here
>> Arguments.names(A, :go)

Ответ 5

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

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

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