2016-12-13 6 views
3

(Crossposting note:1週間前にRuby Forumに質問しましたが、まだ回答がありませんでした)。ここで(単項)Procオブジェクトにパラメータをバインドすることはできますか?

私がこれまで持っているものの(非常に)単純化し、作業バージョンです:Tが構築される方法に応じて、T番号の呼び出しは、どちらか を呼び出すS#s_method1、この例では

# A class S with two methods, one which requires one parameter, and 
# one without parameters. 
class S 
    def initialize(s); @ms = s; end 
    def s_method1(i); puts "s_method1 #{i} #{@ms}"; end 
    def s_method2; puts "s_method2 #{@ms}"; end 
end 

# A class T which uses S, and "associates" itself to 
# one of the both methods in S, depending on how it is 
# initialized. 
class T 
    def initialize(s, choice=nil) 
    @s = S.new(s) 
    # If choice is true, associate to the one-parameter-method, otherwise 
    # to the parameterless method. 
    @pobj = choice ? lambda { @s.s_method1(choice) } : @s.method(:s_method2) 
    end 

    # Here is how I use this association 
    def invoke 
    @pobj.call 
    end 
end 

またはS#S_method2を呼び出しますが、 S#s_method1を呼び出す場合、sオブジェクトのパラメータは、Tオブジェクトの時刻 の作成時にすでに固定されています。このため、2行以下、

T.new('no arguments').invoke 
T.new('one argument', 12345).invoke 

は私が必要とする正確に何である出力

s_method2 no arguments 
s_method1 12345 one argument 

を作り出します。私の質問に今

:私は パラメータのないメソッドs_method2を呼び出したい場合には

choiceゼロである、つまり、私は エレガントな方法によってで私の呼び出し可能オブジェクトを取得することができますchoiceゼロ非あり、Iはconstruにあった場合に

@s.method(:s_method2) 

ct a Procオブジェクト 「ラムダ」を使用しています。これは不器用に見えるだけでなく、少しでも気分が良くなります 不快です。ここでは、initializeメソッド内の 環境に接続されているクロージャがありますが、この が状況によってはメモリリークを引き起こす原因になるかどうかはわかりません。

は、単純に固定された引数にメソッドオブジェクト(この場合は @s.method(:s_method1)をバインドする簡単な方法はありますか?

私の最初のアイデアは

@s.method(:s_method1).curry[choice] 

を使用していたが、これは実現しません私の目標は呼び出し可能なProcオブジェクトを返しませんが、実際にはs_method1を実行します(これはバグではありませんが、文書化された動作です)。

私の目標達成のための他のアイデアはありますか?

+0

そこにあなたのラムダ、それは貧しい男のカリン__is__です。 –

+0

私は知っている、と私はまた、いくつかの豊かな男のカリングが利用できることを望んでいた;-) – user1934428

+1

私の頭の上に、私はあなたがルビーで得ることができる最高だと思う。 –

答えて

3

保存するパラメータは、別途

このオプションは単純ですが、それはあなたが探しているものではないかもしれません:デフォルトパラメータのための

class T 
    def initialize(s, choice=nil) 
    s = S.new(s) 
    @choice = choice 
    @pobj = s.method(choice ? :s_method1 : :s_method2) 
    end 

    def invoke 
    @pobj.call(*@choice) 
    end 
end 

T.new('no arguments').invoke 
T.new('one argument', 12345).invoke 
#=> s_method2 no arguments 
#=> s_method1 12345 one argument 

方法の改良(ルビー2。0+)

# Allows setting default parameters for methods, after they have been defined. 
module BindParameters 
    refine Method do 
    def default_parameters=(params) 
     @default_params = params 
    end 

    def default_parameters 
     @default_params || [] 
    end 

    alias_method :orig_call, :call 

    def call(*params) 
     merged_params = params + (default_parameters[params.size..-1] || []) 
     orig_call(*merged_params) 
    end 
    end 
end 

はここに例を示します

def f(string) 
    puts "Hello #{string}" 
end 

def g(a, b) 
    puts "#{a} #{b}" 
end 

using BindParameters 

f_method = method(:f) 
f_method.default_parameters = %w(World) 

f_method.call('user') # => Hello user 
f_method.call   # => Hello World 

g_method = method(:g) 
g_method.default_parameters = %w(Hello World) 

g_method.call     # => Hello World 
g_method.call('Goodbye')   # => Goodbye World 
g_method.call('Goodbye', 'User') # => Goodbye User 

あなたのコードは書き換えることができます。

class T 
    using BindParameters 
    def initialize(s, *choice) 
    s = S.new(s) 
    @pobj = s.method(choice.empty? ? :s_method2 : :s_method1) 
    @pobj.default_parameters = choice 
    end 

    def invoke 
    @pobj.call 
    end 
end 

T.new('no arguments').invoke   # => s_method2 no arguments 
T.new('one argument', 12_345).invoke # => s_method1 12345 one argument 

モンキー・パッチ適用方法クラス(ルビー1.9+)

それならばMethodクラスにパッチを当てることができます。

class Method 
    def default_parameters=(params) 
    @default_params = params 
    end 

    def default_parameters 
    @default_params || [] 
    end 

    alias_method :orig_call, :call 

    def call(*params) 
    merged_params = params + (default_parameters[params.size..-1] || []) 
    orig_call(*merged_params) 
    end 
end 

Tは次のようになります。

class T 
    def initialize(s, *choice) 
    s = S.new(s) 
    @pobj = s.method(choice.empty? ? :s_method2 : :s_method1) 
    @pobj.default_parameters = choice 
    end 

    def invoke 
    @pobj.call 
    end 
end 

ラッピングメソッドクラス(ルビー1.9+)

この方法では、おそらくあなたがメソッドのクラスを汚染したくない場合はクリーナーです:

class MethodWithDefaultParameters 
    attr_accessor :default_parameters 
    attr_reader :method 

    def initialize(receiver, method_symbol) 
    @method = receiver.public_send(:method, method_symbol) 
    @default_parameters = [] 
    end 

    def call(*params) 
    merged_params = params + (default_parameters[params.size..-1] || []) 
    method.call(*merged_params) 
    end 

    def method_missing(sym, *args) 
    method.send(sym, *args) 
    end 
end 

Tは次のようになります。

class T 
    def initialize(s, *choice) 
    s = S.new(s) 
    @pobj = MethodWithDefaultParameters.new(s, choice.empty? ? :s_method2 : :s_method1) 
    @pobj.default_parameters = choice 
    end 

    def invoke 
    @pobj.call 
    end 
end 

コメントやご提案は大歓迎です!

+0

私はBindParametersのアプローチが本当に好きです(私はRuby 1.9.3を使っているので、今はうまくいきませんが、まだ 'refine'と' using'はありません)。それは現在の問題に対する残虐行為ですが、私はこれを他の文脈でも使うことができると思います。 – user1934428

+1

あなたの次の質問にRubyのバージョン要件を言及してください;)私はRuby 1.9で動作するはずの2つのスクリプトで答えを更新しました。 –

+0

ありがとう!私はあなたの 'MethodWithDefaultParameters'が気に入っています。なぜなら、IMOは、他のクラスから一番きれいな分離です! – user1934428

関連する問題