Ruby:自动将实例变量设置为方法参数?

Kel*_*vin 9 ruby methods instance-variables argument-passing

是否有任何计划实现类似于在方法参数列表中指定实例变量名称的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
Run Code Online (Sandbox Code Playgroud)

我知道这个问题:在Ruby中我可以在initialize方法中以某种方式自动填充实例变量吗?,但所有的解决方案(包括我自己的)似乎都不符合ruby简单哲学.

并且,这种行为会有任何缺点吗?

UPDATE

其中一个原因是红宝石社区的DRY(不要重复自己)哲学.我经常发现自己需要重复参数变量的名称,因为我希望将它分配给同名的实例变量.

def initialize(name)
  # not DRY
  @name = name
end
Run Code Online (Sandbox Code Playgroud)

我能想到的一个缺点是,如果一个方法没有正文,它可能看起来就像什么都没做.如果您正在快速扫描,这可能看起来像一个无操作.但我认为,只要有时间,我们就可以适应.

另一个缺点:如果您在正文中设置其他实例变量,并且您尝试通过将所有分配放在开头可读,则可能需要更多的认知"权力"才能看到分配也发生在参数列表中.但我不认为这比看到一个常量或方法调用并且不得不跳到它的定义更难.

# notice: instance var assignments are happening in 2 places! 
def initialize(@name)
  @errors = []
end
Run Code Online (Sandbox Code Playgroud)

Kel*_*vin 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
Run Code Online (Sandbox Code Playgroud)

这是用法:

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
Run Code Online (Sandbox Code Playgroud)

输出:

[:iv_name, "Methuselah"]
[:age, 969]
[:@name, "Methuselah"]
[:@age, nil]
#<User:0x00000101089448 @name="Methuselah">
Run Code Online (Sandbox Code Playgroud)

它不允许你有一个空的方法体,但它是 DRY 的。我确信它可以通过仅指定哪些方法应该具有此行为(通过 alias_method 实现)来进一步增强,而不是在每个方法中调用 slurp_args - 规范必须在定义所有方法之后。

请注意,模块和辅助方法名称可能可以改进。我只是使用了我想到的第一个东西。