2012-06-01 4 views
9

Gibt es Pläne, Ruby-Verhalten ähnlich dem CoffeeScript-Feature zu implementieren, indem Sie einen Instanzvariablennamen in einer Methodenargumentliste angeben? WieRuby: Instanzvariable automatisch als Methodenargument setzen?

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 

Ich bin diese Frage bewusst: in Ruby can I automatically populate instance variables somehow in the initialize method?, aber alle Lösungen (einschließlich meiner eigenen) scheinen nicht die Rubin Einfachheit Philosophie zu passen.

Und, würde es irgendwelche Nachteile für dieses Verhalten geben?

UPDATE

Einer der Gründe dafür ist die DRY (nicht selbst wiederholen) Philosophie der Ruby-Community. Ich muss oft den Namen einer Argumentvariablen wiederholen, weil ich möchte, dass sie der gleichnamigen Instanzvariablen zugewiesen wird.

Ein Nachteil, den ich mir vorstellen kann ist, dass es so aussieht, als würde eine Methode nichts tun, wenn sie keinen Körper hat. Wenn Sie schnell scannen, kann dies wie ein No-Op aussehen. Aber ich denke, mit der Zeit können wir uns anpassen. Ein weiterer Nachteil: Wenn Sie andere Instanzvariablen im Körper setzen und versuchen, lesbar zu sein, indem Sie alle Zuordnungen am Anfang platzieren, kann es kognitivere "Kraft" geben, um zu sehen, dass dort auch Zuweisungen stattfinden die Argumentliste. Aber ich denke nicht, dass dies schwieriger ist, als beispielsweise einen konstanten oder Methodenaufruf zu sehen und zu seiner Definition springen zu müssen.

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

Antwort

4

Nach einigem Nachdenken, fragte ich mich, ob es möglich ist, die Argumentnamen tatsächlich von einer Rubin-Methode zu bekommen. Wenn ja, könnte ich ein spezielles Argument-Präfix wie "iv_" verwenden, um anzugeben, welche Argumente als Instanzvariablen gesetzt werden sollen.

Und es ist möglich: How to get argument names using reflection.

Ja! Also kann ich vielleicht ein Modul schreiben, das das für mich erledigt. Dann blieb ich stecken, denn wenn ich die Hilfsmethode des Moduls aufruft, kennt sie die Werte der Argumente nicht, weil sie für den Aufrufer lokal sind. Ah, aber Rubin hat verbindliche Objekte.

Hier ist das Modul (Ruby 1.9 only):

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 

Und hier ist die Nutzung:

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 

Ausgang:

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

Es Sie eine leere haben nicht lassen Methodenkörper, aber es ist DRY. Ich bin mir sicher, dass es weiter verbessert werden kann, indem nur angegeben wird, welche Methoden dieses Verhalten haben sollen (implementiert über alias_method), anstatt slurp_args in jeder Methode aufzurufen - die Spezifikation müsste dann sein, nachdem alle Methoden definiert wurden.

Beachten Sie, dass der Name der Modul- und Hilfsmethode wahrscheinlich verbessert werden könnte. Ich habe nur das erste, was mir in den Sinn kam, benutzt.

+0

Danke für diese Antwort. Es scheint definitiv mehr Arbeit zu sein als es sich lohnt. :) – hrdwdmrbl

+0

@jackquack gut, du musst das Setup wirklich nur einmal machen. Dann müssen Sie in Ihren Klassen nur 'enable_for' und' slurp_args' aufrufen. – Kelvin

0

Ich denke, Sie haben Ihre eigene Frage beantwortet, es passt nicht die Rubin Einfachheit Philosophie. Es würde zusätzliche Komplexität für die Behandlung von Parametern in Methoden hinzufügen und die Logik zum Verwalten von Variablen in die Methodenparameter verschieben. Ich kann das Argument sehen, dass es den Code weniger lesbar macht, aber ich finde es nicht sehr ausführlich.

Einige Szenarien die @param mit zu kämpfen haben würde:

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

def search(@query, condition) 

def ratchet(@*arg ) 

Sollten alle diese Szenarien gültig? Nur die initialize? Die @*arg scheint in meiner Meinung besonders heikel. All diese Regeln und Ausschlüsse machen die Ruby-Sprache komplizierter. Zum Vorteil von Variablen für die automatische Instanz würde es sich meiner Meinung nach nicht lohnen.

+2

Ich würde eine Splatted Instanz Arg vorstellen, um '* @ arg' zu sein. Ansonsten scheint keines der Szenarien besonders hässlich, kompliziert oder sogar schwer zu folgen. In Bezug auf die instance var logic können Sie sie bereits in der Arg-Liste setzen, wenn auch auf eine konstruierte Art: 'def run (a = (@ v = 1)); Ende. Ja, es ist hässlich erfunden, und die var wird nur gesetzt, wenn kein arg übergeben wird. Aber es zeigt, dass diese Art von Zuweisung bereits erlaubt ist. Ich denke, es hilft Code-DRYer zu halten. Ich füge ein weiteres Beispiel zu meiner Frage hinzu. – Kelvin

+0

Whoops, als ich sagte, es macht Code DRYer, meinte ich den Vorschlag in meiner Frage, nicht das erfundene Beispiel in meinem Kommentar. – Kelvin

2

Nun, eigentlich ...

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

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

Arbeiten in 1.8.7, aber nicht in 1.9.3. Nun, nur wo habe ich darüber gelernt ...

+2

+1 für einen tapferen Versuch. Habe es noch nicht ausprobiert, aber es ist eher ein Novum, wenn es am 1.9.3 nicht funktioniert. – Kelvin

Verwandte Themen