Warum brauchen Ruby-Setter "Selbst"? Qualifikation innerhalb der Klasse?


75

Ruby-Setter - ob von (c)attr_accessoroder manuell erstellt - scheinen die einzigen Methoden zu sein, die self.beim Zugriff innerhalb der Klasse selbst qualifiziert werden müssen. Dies scheint Ruby allein in die Welt der Sprachen zu bringen:

  • Alle Methoden benötigen self/ this(wie Perl und ich denke Javascript)
  • Keine Methoden erfordern self/ thisist (C #, Java)
  • Nur Setter brauchen self/ this(Ruby?)

Der beste Vergleich ist C # mit Ruby, da beide Sprachen Zugriffsmethoden unterstützen, die syntaktisch genau wie Klasseninstanzvariablen funktionieren: foo.x = y, y = foo.x. C # nennt sie Eigenschaften.

Hier ist ein einfaches Beispiel; das gleiche Programm in Ruby dann C #:

class A
  def qwerty; @q; end                   # manual getter
  def qwerty=(value); @q = value; end   # manual setter, but attr_accessor is same 
  def asdf; self.qwerty = 4; end        # "self." is necessary in ruby?
  def xxx; asdf; end                    # we can invoke nonsetters w/o "self."
  def dump; puts "qwerty = #{qwerty}"; end
end

a = A.new
a.xxx
a.dump

Nehmen Sie das weg self.qwerty =()und es schlägt fehl (Ruby 1.8.6 unter Linux & OS X). Jetzt C #:

using System;

public class A {
  public A() {}
  int q;
  public int qwerty {
    get { return q; }
    set { q = value; }
  }
  public void asdf() { qwerty = 4; } // C# setters work w/o "this."
  public void xxx()  { asdf(); }     // are just like other methods
  public void dump() { Console.WriteLine("qwerty = {0}", qwerty); }
}

public class Test {
  public static void Main() {
    A a = new A();
    a.xxx();
    a.dump();
  }
}

Frage: Ist das wahr? Gibt es neben Setzern noch andere Gelegenheiten, bei denen Selbst notwendig ist? Gibt es andere Fälle, in denen eine Ruby-Methode nicht ohne Selbst aufgerufen werden kann ?

Sicherlich gibt es viele Fälle , in denen selbst wird notwendig. Dies gilt nicht nur für Ruby, um ganz klar zu sein:

using System;

public class A {
  public A() {}
  public int test { get { return 4; }}
  public int useVariable() {
    int test = 5;
    return test;
  }
  public int useMethod() {
    int test = 5;
    return this.test;
  }
}

public class Test {
  public static void Main() {
    A a = new A();
    Console.WriteLine("{0}", a.useVariable()); // prints 5
    Console.WriteLine("{0}", a.useMethod());   // prints 4
  }
}

Dieselbe Mehrdeutigkeit wird auf dieselbe Weise gelöst. Aber während ich subtil bin, frage ich nach dem Fall, wo

  • Ein Verfahren ist definiert worden, und
  • Es wurde keine lokale Variable definiert, und

wir begegnen

qwerty = 4

Was ist mehrdeutig - ist dies ein Methodenaufruf oder eine neue lokale Variablenzuweisung?


@ Mike Stone

Hallo! Ich verstehe und schätze die Punkte, die Sie gemacht haben, und Ihr Beispiel war großartig. Glauben Sie mir, wenn ich sage, wenn ich genug Ruf hätte, würde ich Ihre Antwort abstimmen. Wir sind uns jedoch nicht einig:

  • auf eine Frage der Semantik, und
  • auf einen zentralen Punkt der Tatsache

Zuerst behaupte ich, nicht ohne Ironie, wir führen eine semantische Debatte über die Bedeutung von "Mehrdeutigkeit".

Wenn es um die Analyse und Programmiersprachen-Semantik geht (das Thema dieser Frage), würden Sie sicherlich ein breites Spektrum des Begriffs "Mehrdeutigkeit" zugeben. Nehmen wir einfach eine zufällige Notation an:

  1. mehrdeutig: lexikalische Mehrdeutigkeit (Lex muss nach vorne schauen)
  2. Mehrdeutig: grammatikalische Mehrdeutigkeit (yacc muss sich der Analyse des Analysebaums unterziehen)
  3. AMBIGUOUS: Mehrdeutigkeit, die im Moment der Ausführung alles weiß

(und es gibt auch Müll zwischen 2-3). Alle diese Kategorien werden aufgelöst, indem mehr Kontextinformationen gesammelt werden, die immer globaler aussehen. Also, wenn du sagst,

"qwerty = 4" ist in C # UNAMBIGUOUS, wenn keine Variable definiert ist ...

Ich konnte nicht mehr zustimmen. Aber aus dem gleichen Grund sage ich

"qwerty = 4" ist in Rubin nicht mehrdeutig (wie es jetzt existiert)

"qwerty = 4" ist in C # mehrdeutig

Und wir widersprechen uns noch nicht. Schließlich sind wir uns hier nicht einig: Entweder könnte Ruby ohne weitere Sprachkonstrukte implementiert werden oder nicht, so dass

Für "qwerty = 4" ruft ruby ​​UNAMBIGUOUSLY einen vorhandenen Setter auf, wenn
keine lokale Variable definiert ist

Du sagst nein. Ich sage ja; Es könnte ein anderer Ruby existieren, der sich in jeder Hinsicht genau wie der Strom verhält, außer dass "qwerty = 4" eine neue Variable definiert, wenn kein Setter und kein lokaler vorhanden ist. Er ruft den Setter auf, falls einer vorhanden ist, und weist ihn dem lokalen zu, wenn einer vorhanden ist. Ich akzeptiere voll und ganz, dass ich falsch liegen könnte. In der Tat wäre ein Grund, warum ich falsch liegen könnte, interessant.

Lassen Sie mich erklären.

Stellen Sie sich vor, Sie schreiben eine neue OO-Sprache mit Zugriffsmethoden, die wie Instanzvariablen aussehen (wie Ruby & C #). Sie würden wahrscheinlich mit konzeptionellen Grammatiken beginnen wie:

  var = expr    // assignment
  method = expr // setter method invocation

Aber der Parser-Compiler (nicht einmal die Laufzeit) wird kotzen, denn selbst nachdem alle Eingaben bearbeitet wurden, gibt es keine Möglichkeit zu wissen, welche Grammatik relevant ist. Sie stehen vor einer klassischen Wahl. Ich kann mir der Details nicht sicher sein, aber im Grunde macht Ruby das:

  var = expr    // assignment (new or existing)
  // method = expr, disallow setter method invocation without .

Deshalb ist es nicht eindeutig, während und C # dies tut:

  symbol = expr // push 'symbol=' onto parse tree and decide later
                // if local variable is def'd somewhere in scope: assignment
                // else if a setter is def'd in scope: invocation

Für C # befindet sich "später" noch zur Kompilierungszeit.

Ich bin sicher, Ruby könnte das Gleiche tun, aber "später" müsste zur Laufzeit sein, denn wie Ben betont, weiß man erst, wenn die Anweisung ausgeführt wird, welcher Fall zutrifft.

Meine Frage sollte niemals bedeuten: "Brauche ich wirklich das 'Selbst'?" oder "Welche potenzielle Mehrdeutigkeit wird vermieden?" Ich wollte eher wissen, warum diese besondere Wahl getroffen wurde. Vielleicht ist es keine Leistung. Vielleicht hat es gerade die Arbeit erledigt, oder es wurde als das Beste angesehen, einem 1-Liner-Einheimischen immer zu erlauben, eine Methode zu überschreiben (eine ziemlich seltene Fallanforderung) ...

Aber ich schlage vor, dass die dynamischste Sprache diejenige sein könnte, die diese Entscheidung am längsten aufschiebt, und wählt die Semantik basierend auf den kontextuellsten Informationen: Wenn Sie also keine lokale Sprache haben und einen Setter definiert haben, wird der Setter verwendet . Ist das nicht der Grund, warum wir Ruby, Smalltalk, Objc mögen, weil der Methodenaufruf zur Laufzeit entschieden wird und maximale Ausdruckskraft bietet?


PHP erfordert auch $this->beim Zugriff auf Instanzvariablen. Das stolpert mich die ganze Zeit.
Chloe

Ein expliziter Empfänger wird nur für Klassenmethoden benötigt, nicht für Instanzmethoden.
Todd A. Jacobs

Ich stimme zu - ich mag diese Methode zur Auflösung der Amibuität auch nicht. Verstößt gegen das Prinzip der geringsten Überraschung.
Dogweather

@Dogweather Matz erklärt mit geringster Überraschung , was er meint : Jeder hat einen individuellen Hintergrund. ... sie können von verschiedenen Aspekten der Sprache überrascht sein. Dann kommen sie auf mich zu und sagen: "Ich war von diesem Merkmal der Sprache überrascht, deshalb verstößt Ruby gegen das Prinzip der geringsten Überraschung." Warten. Warten. Das Prinzip der geringsten Überraschung gilt nicht nur für Sie. Das Prinzip der geringsten Überraschung bedeutet Prinzip der geringsten meiner Überraschung. Und es bedeutet das Prinzip der geringsten Überraschung, nachdem Sie Ruby sehr gut gelernt haben.
Kelvin

Antworten:


19

Das Wichtigste dabei ist, dass Ruby-Methoden zu jedem Zeitpunkt (un) definiert werden können. Um die Mehrdeutigkeit intelligent aufzulösen, müsste für jede Zuweisung Code ausgeführt werden, um zu überprüfen, ob zu diesem Zeitpunkt eine Methode mit dem zugewiesenen Namen vorhanden ist der Zuordnung.


18
Dies ist nicht korrekt. Der eigentliche Grund dafür ist, dass durch einfaches x=5Angeben eine lokale Variable instanziiert wird, xdie jeden vorhandenen Setter überschreibt self.x=. Um diese Unklarheit zu x=self.x=
beseitigen

85

Nun, ich denke, der Grund dafür ist, dass dies nicht qwerty = 4eindeutig ist - definieren Sie eine neue Variable namens qwertyoder rufen Sie den Setter auf? Ruby löst diese Mehrdeutigkeit, indem es sagt, dass eine neue Variable erstellt wird, daher self.ist die erforderlich.

Hier ist ein weiterer Fall, in dem Sie benötigen self.:

class A
  def test
    4
  end
  def use_variable
    test = 5
    test
  end
  def use_method
    test = 5
    self.test
  end
end
a = A.new
a.use_variable # returns 5
a.use_method   # returns 4

Wie Sie sehen können, ist der Zugriff auf nicht testeindeutig, daher self.ist der Zugriff erforderlich.

Aus diesem Grund ist das C # -Beispiel eigentlich kein guter Vergleich, da Sie Variablen auf eine Weise definieren, die für die Verwendung des Setters eindeutig ist. Wenn Sie in C # eine Variable definiert hätten, die denselben Namen wie der Accessor hat, müssten Sie Anrufe an den Accessor this.genau wie im Fall Ruby qualifizieren.


17

Denn sonst wäre es unmöglich, innerhalb von Methoden überhaupt lokale Variablen zu setzen. variable = some_valueist nicht eindeutig. Zum Beispiel:

class ExampleClass
  attr_reader :last_set
  def method_missing(name, *args)
    if name.to_s =~ /=$/
      @last_set = args.first
    else
      super
    end
  end

  def some_method
    some_variable = 5 # Set a local variable? Or call method_missing?
    puts some_variable
  end
end

Wenn selfnicht für Setter erforderlich, some_methodwürde erhöhen NameError: undefined local variable or method 'some_variable'. Wie es ist, funktioniert die Methode wie beabsichtigt:

example = ExampleClass.new
example.blah = 'Some text'
example.last_set #=> "Some text"
example.some_method # prints "5"
example.last_set #=> "Some text"

8
Haha. Ich wollte diese Antwort positiv bewerten und stellte dann fest, dass es meine vor einem Jahr war. ;-)
Ajedi32

Ich denke, dies ist die einzige Antwort, die klar angibt, warum man niemals darauf verzichten kann self..
Amöbe

1
Ich sollte dem hinzufügen, dass Java und seine Kollegen damit davonkommen, weil der Compiler als statisch typisierte Sprachen im Voraus feststellen kann, ob es zu einem Konflikt kommen wird, und dementsprechend einen entsprechenden Fehler auslösen kann. Dynamische (Skriptsprachen), schwache (PHP) und Enten (Ruby / Python) Sprachen haben leider keinen solchen Luxus
Shayne
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.