2010-01-07 97 views
10

我发现自己经常使用命名参数为方法编写我认为不需要的Ruby代码。将命名参数命名为Ruby中的局部变量

就拿下面的代码:

def my_method(args) 
    orange = args[:orange] 
    lemon = args[:lemon] 
    grapefruit = args[:grapefruit] 

    # code that uses 
    # orange, lemon & grapefruit in this format which is way prettier & concise than 
    # args[:orange] args[:lemon] args[:grapefruit] 

    puts "my_method variables: #{orange}, #{lemon}, #{grapefruit}" 
end 
my_method :orange => "Orange", :grapefruit => "Grapefruit" 

我真的不喜欢这个代码是什么,我不得不采取指定参数和值传递到本地变量违背DRY原则和公正一般会占用我的方法空间。如果我不使用局部变量,只是用args [:symbol]语法引用所有变量,那么代码就会变得难以辨认。

我已经试过了工作解决这个,但保持创下了砖墙,因为我不知道如何使用该方法的范围EVAL,或使用任何其他技术来定义局部变量。这里是低于许多尝试之一,这会导致错误

def my_method_with_eval(args) 
    method_binding = binding 
    %w{ orange lemon grapefruit}.each { |variable| eval "#{variable} = args[:#{variable}]", method_binding; } 

    # code that uses 
    # orange, lemon & grapefruit in this format which is way prettier & concise than 
    # args[:orange] args[:lemon] args[:grapefruit] 

    puts "my_method_with_eval variables: #{orange}, #{lemon}, #{grapefruit}" 
end 
my_method_with_eval :orange => "Orange", :grapefruit => "Grapefruit" 

当运行的代码,我只是得到

NameError: undefined local variable or method ‘orange’ for main:Object method my_method_with_eval in named_args_to_local_vars at line at top level in named_args_to_local_vars at line 9

任何人有任何想法如何,我可以以某种方式简化了下来,让我不必使用var = args [:var]代码加载启动我的命名参数方法?

感谢, Matthew O'Riordan

+0

听起来像你真正想要的是命名参数。这些存在于Ruby 2.0中。 – Ajedi32 2013-08-18 14:03:34

回答

6

我不相信有什么办法在Ruby中做到这一点(如果有人想出了一个,请让我知道,我会更新或删除这个答案,以反映它!) - 如果尚未定义局部变量,则无法通过绑定动态定义它。你当然可以这样做orange, lemon, grapefruit = nil调用eval之前,但是你可能会遇到其他问题 - 例如,如果ARGS [:橙]是字符串“橙”,你最终会与当前的评价执行orange = Orange

这里的东西可以工作,不过,使用标准库中的OpenStruct类(由“可以工作”,我的意思是“这完全取决于你的时尚感a.orange是否比任何args[:orange]更好”):

require 'ostruct' 

def my_method_with_ostruct(args) 
    a = OpenStruct.new(args) 
    puts "my_method_with_ostruct variables: #{a.orange}, #{a.lemon}, #{a.grapefruit}" 
end 

如果您不需要容易获得这种方法的接收机的任何国家或方法,你可以使用instance_eval,如下所示。

def my_method_with_instance_eval(args) 
    OpenStruct.new(args).instance_eval do 
    puts "my_method_with_instance_eval variables: #{orange}, #{lemon}, #{grapefruit}" 
    end 
end 

你甚至可以做something tricky with method_missing(见here更多)允许访问“主”对象,但性能可能不会很大。

总而言之,我认为这可能是最简单的/可读一起去困扰你少干初步的解决方案。

+0

感谢您的意见。在尝试寻找解决方案还有几天之后,我认为您的建议要保持现在的状态可能是最好的!尝试将变量添加到local_variables似乎非常困难,而且不是Ruby鼓励的。我确实看到过去有一个扩展Binding.caller(在http://extensions.rubyforge.org/rdoc/index.html),它提供了获取调用绑定和插入局部变量的功能,但是这已经弃用。 – 2010-01-10 13:51:11

3

我发现ruby-talk-google这个讨论,似乎是解析器的优化。局部变量已经在运行时计算出来,所以local_variables已经在方法的开始处被设置了。

def meth 
    p local_variables 
    a = 0 
    p local_variables 
end 
meth 
# => 
[:a] 
[:a] 

这样的Ruby并不需要决定是否a在运行时的方法或局部变量或诸如此类的东西,但可以有把握地认为它是一个局部变量。

(对比:在Python locals()将在函数的开头空)

+0

该主题中有很多很好的信息 - 很好找。 – 2010-01-08 03:18:03

1

在我的博客(见用户信息的链接),我只是试图解决巧妙地处理这个问题。我进入更详细那里,但我的解决方案的核心是下面的辅助方法:

def collect_named_args(given, expected) 
    # collect any given arguments that were unexpected 
    bad = given.keys - expected.keys 

    # if we have any unexpected arguments, raise an exception. 
    # Example error string: "unknown arguments sonething, anyhting" 
    raise ArgumentError, 
     "unknown argument#{bad.count > 1 ? 's' : ''}: #{bad.join(', ')}", 
     caller unless bad.empty? 

    Struct.new(*expected.keys).new(
     *expected.map { |arg, default_value| 
      given.has_key?(arg) ? given[arg] : default_value 
     } 
    ) 
end # def collect_named_args 

被称为如下:

def foo(arguments = {}) 
    a = collect_named_args(arguments, 
     something: 'nothing', 
     everything: 'almost', 
     nothing:  false, 
     anything: 75) 

    # Do something with the arguments 
    puts a.anything 
end # def foo 

我还在试图找出是否有以任何方式将我的结果导入到local_variables中 - 但正如其他人所指出的那样,Ruby并不想这么做。我想,你可以使用“with”技巧。

module Kernel 
    def with(object, &block) 
    object.instance_eval &block 
    end 
end 

然后

with(a) do 
    # Do something with arguments (a) 
    put anything 
end 

但有几个原因感到不满意。

我喜欢上面的解决方案,因为它使用了一个Struct而不是OpenStruct,这意味着少一个需求,并且设置了哪些变量。

6

格雷格与沙的答案合并:

require 'ostruct' 

def my_method(args = {}) 
    with args do 
    puts a 
    puts b 
    end 
end 

def with(args = {}, &block) 
    OpenStruct.new(args).instance_eval(&block) 
end 

my_method(:a => 1, :b => 2) 
+0

这似乎是一个非常漂亮的解决方案,可能非常有用 – RubyFanatic 2011-08-17 01:26:34

+0

美丽而优雅的方法。很棒。感谢分享! – plang 2012-05-10 09:08:23

0

这并没有解决问题,但我倾向于做

orange, lemon, grapefruit = [:orange, :lemon, :grapefruit]. 
map{|key| args.fetch(key)} 

,因为它是很容易复制和粘贴orange lemon grapefruit位。

如果您发现冒号太多的工作,你可以做

orange, lemon, grapefruit = %w{orange, lemon, grapefruit}. 
map{|str| str.gsub(",", "").to_sym}.map{|key| args.fetch(key)}