2012-06-01 91 views
9

是否有任何計劃實現類似於CoffeeScript功能的ruby行爲在方法參數列表中指定實例變量名稱? 像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 

我知道這個問題的:in Ruby can I automatically populate instance variables somehow in the initialize method?,但所有的解決方案(包括我自己)似乎並不適合簡單的紅寶石理念。

而且,這種行爲會有什麼缺點嗎?

UPDATE

其中的一個原因,這是DRY(不要重複自己)Ruby社區的理念。我經常發現自己需要重複一個參數變量的名稱,因爲我想將它分配給同名的實例變量。

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

我可以想到的一個缺點是,它可能看起來好像一個方法沒有任何功能,如果它沒有身體。如果你正在快速掃描,這可能看起來像沒有任何操作。但我覺得有時間,我們可以適應。另一個缺點是:如果你在正文中設置了其他實例變量,並且你試圖通過把所有的任務放在開頭來讀取它,那麼它可能需要更多的認知「權力」來看到在那裏也有分配參數列表。但我不認爲這比看到一個常數或方法調用並且不得不跳到它的定義更困難。

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

回答

4

經過一番思考,我想知道是否有可能實際從ruby方法獲取參數名稱。如果是這樣,我可以使用像「iv_」這樣的特殊參數前綴來指示哪些參數應該設置爲實例變量。

而且有可能:How to get argument names using reflection

是的!所以我可以寫一個模塊來爲我處理這個問題。然後我卡住了,因爲如果我調用模塊的幫助器方法,它不知道參數的值,因爲它們對調用者來說是本地的。啊,但紅寶石有綁定對象。

這裏的模塊(紅寶石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"> 

它不會讓你有一個空方法體,但它是乾的。我相信只要指定哪些方法應該具有這種行爲(通過alias_method實現),而不是在每個方法中調用slurp_args,就可以進一步增強它的性能 - 規範必須在所有方法都已定義之後進行。

請注意,模塊和輔助方法名稱可能可能會改進。我只是想到了第一件事。

+0

感謝您的回答。它絕對看起來像更多的工作,但它是值得的。 :) – hrdwdmrbl

+0

@jackquack好吧,你真的只需要做一次安裝。然後在你的課程中,你所要做的就是調用'enable_for'和'slurp_args'。 – Kelvin

0

我想你回答了你自己的問題,它不符合紅寶石的簡單性哲學。如果在方法中處理參數並將用於管理變量的邏輯上移到方法參數中,會增加額外的複雜性。我可以看到這樣一個觀點,即它使得代碼不易被理解,但它確實使我不那麼冗長。

一些情景@ PARAM將不得不與之抗衡:

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

def search(@query, condition) 

def ratchet(@*arg ) 

所有這些情況下應該是有效的?只是initialize?在我看來,@*arg似乎特別冒險。所有這些規則和排除使Ruby語言更加複雜。爲了自動實例變量的好處,我認爲這不值得。

+2

我想象一個splatted實例arg是'* @ arg'。除此之外,沒有一種情景看起來特別醜陋,複雜甚至難以遵循。就實例var邏輯而言,您可以將它們設置在arg列表中,儘管以一種人爲的方式:'def run(a =(@ v = 1)); end'。是的,這是醜陋的設計,如果沒有arg通過,var只會被設置。但它表明這種分配已經被允許。我認爲它有助於保持代碼乾燥。我將在我的問題上再添一個例子。 – Kelvin

+0

哎呀,當我說它使代碼DRYer時,我的意思是提出我的問題,而不是我評論中人爲的例子。 – Kelvin

2

嗯,其實...

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

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

工程在1.8.7,但不是在1.9.3。現在,只是其中我是否瞭解這個...

+2

勇敢嘗試+1。還沒有嘗試過,但如果它在1.9.3上不起作用,它更加新奇。 – Kelvin

相關問題