2010-01-07 82 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)}