2009-03-07 33 views
22

Rubyでかなり頑丈なリフレクションをしたいと思います。呼び出しスタックの上位にあるさまざまな呼び出し関数の引数の名前を返す関数を作成したいとします(ただ1つで十分でしょうが、なぜそこで停止するのですか?)。私は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か、このタスクのためのいくつかの同様のツールを使用しますが解析ツリーを見て気にしないだろう、それを使用する方法は明らかではありませんこの目的のために。 thisのようなCエクステンションを作成することも別の可能性があります。

私はおそらくC拡張のいくつかの種類が必要になることがわかります。私は私の質問は、Cの拡張機能の組み合わせが最も簡単に動作するだろうということを意味すると思います。私は、呼び出し元+ ParseTreeだけで十分ではないと思います。

「自動デバッグ」というよりも、私はこれをやりたいのですが、関数の呼び出し条件と戻り条件の自動チェックにこの機能を使用するといいでしょう。

xyが正でなかった場合 check_positiveが例外をスローします
def add x, y 
    check_positive 
    return x + y 
end 

。明らかに、それ以上のことはあるだろうが、これが十分な動機を与えることを願っている。

+0

を使用すると、実行順序と引数を決定したいように見えますがあります。ロギングを使用できない理由はありますか?クラス内のすべてのメソッドを開くためにメタプログラミングを行うこともできますし、自動的に引数を記録して、あなたが望むことをすることができます。 – runako

答えて

15

Merbのaction-argsライブラリを見てみることをお勧めします。

require 'rubygems' 
require 'merb' 

include GetArgs 

def foo(bar, zed=42) 
end 

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

あなたはMerbのに依存したくない場合は、the source code in githubから最良の部分を選択して選ぶことができます。 Rubyの1.9.2で

4

私は非常に高価でほとんどの方法しかありません。

$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に基づくものは、既存のコードに触れる必要がないため、自動化することが可能です。実際、これはset_trace_funcの観点からdebug.rbがどのように実装されているかです。

正確な質問に対する解決策は、基本的には概説したとおりです。呼び出し元+ parsetreeを使用するか、ファイルを開いてそのようにデータを取得します。あなたが議論の名前を得ることができることを認識している反射能力はありません。私の解決策は、関連するメソッドオブジェクトをつかんで、#arityを呼び出してlocal_variablesの何が引数なのかを呼び出すことで承認できますが、その関数の結果が出てくるようですが、それに頼るのは安全ではないでしょうか。あなたが私に尋ねる気にならないなら、あなたが説明しているデータとインターフェースを手に入れたら、それで何をするつもりですか?自動デバッグは、私がこの機能の使用を想像したときに最初に気にしたことではありませんでしたが、おそらく私のところでは想像力に欠けています。


Aha!

私はこれとは異なってアプローチします。 ruby-contract、rdbcなど、すでに契約で設計しているいくつかのRubyライブラリがあります。

別のオプションは次のように書くことである。もちろん

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 

これは、はるかに洗練され行うことができ、実際にそれは私が言及したライブラリが提供するものの一部ですが、おそらく、これは、あなたがそこに着くために使用する予定の道を必ずしも通過せずに、あなたが行きたいところにあなたを得る方法です。実際に

+0

実際に、あなたが記述する方法は、引数をローカル変数から区別することができず、同時に自動的に動作しない場合もあります。 –

+0

これは契約によって設計する場合に機能しますが、呼び出すことのできる単一の関数。理想的なのは、チェックを追加したり削除したりすることが簡単にできることです。 –

-3

、あなたが明確に記述する方法も自動的に

あなたが何をしようとしていることはサポートされているものではありませんので、だその仕事に失敗しながら、ローカル変数から引数を区別するために失敗しました。ルビで可能なことはすべて可能ですが、それを行うための方法や既知の方法はありません。

ロガンが提案したようなバックトレースを評価することもできますし、Cコンパイラを破棄してルビのソースコードをハックすることもできます。私は合理的にこれを行うための他の方法がないと確信しています。

39

、あなたは自明Proc#parametersで(もちろんのも任意のMethodまたはUnboundMethodのため、)任意のProcのパラメータリストを取得することができます。

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

フォーマットはのペアの配列であります記号:タイプ(必須、オプション、残り、ブロック)と名前。あなたが望む形式について

、しかし、まだあなたが呼び出し側にはアクセスできません。もちろん、

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

を、してみてください。

+0

これはデフォルト値も返しません。例: 'def person(名前:" calvin ")'。 'method.parameters'は' [[:key、:name]] 'を返します。 –

+2

デフォルト値は動的に評価され、静的な情報は取得できません。 'def foo(par = rand());メソッド(:foo).parameters' return? –

+0

ここで 'instance_method'とは何ですか? – fraxture

1

デフォルト値の値が必要な場合は、あまりにも、「引数」宝石

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