10

メソッドの引数リストにインスタンス変数名を指定するCoffeeScriptの機能に似たRubyの動作を実装する予定はありますか? お気に入り

class User
  def initialize(@name, age)
    # @name is set implicitly, but @age isn't.
    # the local variable "age" will be set, just like it currently works.
  end
end

私はこの質問を認識しています.Rubyでは、initializeメソッドで何らかの方法でインスタンス変数を自動的に設定できますか? 、しかし、すべての解決策 (私自身のものを含む) は、Ruby のシンプルさの哲学に適合していないようです。

そして、この振る舞いをすることの欠点はありますか?

アップデート

その理由の 1 つは、Ruby コミュニティの DRY (同じことを繰り返すな) 哲学です。同じ名前のインスタンス変数に割り当てたいため、引数変数の名前を繰り返す必要があることがよくあります。

def initialize(name)
  # not DRY
  @name = name
end

私が考えることができる 1 つの欠点は、メソッドに本体がない場合、メソッドが何もしていないように見えることです。すばやくスキャンしている場合、これはノーオペレーションのように見えるかもしれません。しかし、時間があれば、適応できると思います。

もう 1 つの欠点: 本体に他のインスタンス変数を設定していて、最初にすべての代入を配置して読みやすくしようとすると、引数リストでも代入が行われていることを確認するために、より認知的な「力」が必要になる可能性があります。しかし、これは、たとえば、定数またはメソッドの呼び出しを見て、その定義にジャンプしなければならないことよりも難しいとは思いません。

# notice: instance var assignments are happening in 2 places! 
def initialize(@name)
  @errors = []
end
4

3 に答える 3

4

熟考した結果、実際に ruby​​ メソッドから引数名を取得することは可能かどうか疑問に思いました。その場合、"iv_" のような特別な引数プレフィックスを使用して、どの引数をインスタンス変数として設定する必要があるかを示すことができます。

そしてそれは可能です:リフレクションを使用して引数名を取得する方法

はい!ですから、これを処理するモジュールを書くことができるかもしれません。次に、モジュールのヘルパー メソッドを呼び出すと、引数の値が呼び出し元に対してローカルであるため、引数の値がわからないため、行き詰まりました。あ、でも ruby​​ には Binding オブジェクトがある。

モジュールは次のとおりです(ruby 1.9のみ):

module InstanceVarsFromArgsSlurper
  # arg_prefix must be a valid local variable name, and I strongly suggest
  # ending it with an underscore for readability of the slurped args.
  def self.enable_for(mod, arg_prefix)
    raise ArgumentError, "invalid prefix name" if arg_prefix =~ /[^a-z0-9_]/i
    mod.send(:include, self)
    mod.instance_variable_set(:@instance_vars_from_args_slurper_prefix, arg_prefix.to_s)
  end

  def slurp_args(binding)
    defined_prefix = self.class.instance_variable_get(:@instance_vars_from_args_slurper_prefix)
    method_name = caller[0][/`.*?'/][1..-2]
    param_names = method(method_name).parameters.map{|p| p.last.to_s }
    param_names.each do |pname|
      # starts with and longer than prefix
      if pname.start_with?(defined_prefix) and (pname <=> defined_prefix) == 1
        ivar_name = pname[defined_prefix.size .. -1]
        eval "@#{ivar_name} = #{pname}", binding
      end
    end
    nil
  end
end

そして、使用法は次のとおりです。

class User
  InstanceVarsFromArgsSlurper.enable_for(self, 'iv_')

  def initialize(iv_name, age)
    slurp_args(binding)  # this line does all the heavy lifting
    p [:iv_name, iv_name]
    p [:age, age]
    p [:@name, @name]
    p [:@age, @age]
  end
end

user = User.new("Methuselah", 969)
p user

出力:

[:iv_name, "Methuselah"]
[:age, 969]
[:@name, "Methuselah"]
[:@age, nil]
#<User:0x00000101089448 @name="Methuselah">

メソッド本体を空にすることはできませんが、DRY です。各メソッドで slurp_args を呼び出すのではなく、どのメソッドがこの動作を持つべきか (alias_method を介して実装) を指定するだけで、さらに拡張できると確信しています。

モジュールとヘルパー メソッド名はおそらく改善される可能性があることに注意してください。最初に思いついたものをそのまま使いました。

于 2012-06-02T22:13:08.283 に答える
2

ええと、実際には...

class User
  define_method(:initialize) { |@name| }
end

User.new(:name).instance_variable_get :@name
# => :name

1.8.7 では動作しますが、1.9.3 では動作しません。さて、私はどこでこれを知ったのでしょうか...

于 2012-06-02T02:36:39.613 に答える
0

あなたは自分の質問に答えたと思いますが、それはルビーのシンプルさの哲学には合いません。メソッドでパラメーターを処理する方法がさらに複雑になり、変数を管理するためのロジックがメソッド パラメーターに移動します。コードが読みにくくなるという議論は理解できますが、あまり冗長ではないと思います。

@ パラメータが対処しなければならないいくつかのシナリオ:

def initialize( first, last, @scope, @opts = {} )

def search( @query, condition )

def ratchet( @*arg  ) 

これらのシナリオはすべて有効である必要がありますか? ただinitialize?私の@*arg心の中では特に危険に思えます。これらすべてのルールと除外により、Ruby 言語はより複雑になります。自動インスタンス変数の利点については、それだけの価値があるとは思いません。

于 2012-06-01T20:32:16.250 に答える