2016-09-09 1 views
2

Ich versuche zu verstehen, wie Ruby Methoden/Symbole findet, aber ich kämpfe, wenn es mehrere Ebenen betrifft, insbesondere den globalen/Dateibereich.Ruby Methode Lookup in Bezug auf Globals/Scopes

Beim expliziten Aufrufen von Methoden für eine Klasse gibt es viele Illustrationen in der Reihenfolge, in der die Klassen und die von ihnen eingeschlossenen Module gesucht werden (und somit genau das, was super in jedem Fall aufruft). Wenn jedoch nicht explizit eine Methode aufgerufen wird, z.B. eine Ebene func args anstatt self.func args Was ist die Suchreihenfolge?

Warum unten ist in meinem Beispiel das Element-Methode aufrufen func das Element-Methode vor der globalen finden, aber func2 die globale findet ohne method_missing genannt zu werden? Und wenn das globale ist stattdessen ein Modul/Klasse/Typ, warum ist das Mitglied mit dem gleichen Namen überhaupt nicht gefunden?

Gibt es eine offizielle Dokumentation darüber, was die Sprache genau macht, wenn sie in einer Methode "func, func(), func arg" usw. findet? Es gibt viele Blogs von Drittanbietern, aber sie redeten nur wirklich über einzelne Instanzen mit include Module und class Type < BaseType.

def func; "global func" end 
def func2; "global func 2" end 

class Test 
    def x; func end 
    def y; func2 end 
    def z; Math end 
    def w 
    func = "local_var" 
    [func(), func] 
    end 


    def func(arg=nil); "method func" end 
    def func=(x); puts "assign func=" end 
    def Math; "method Math" end 

    def method_missing(sym, *args, &block) 
    puts "method_missing #{sym}" 
    super(sym, *args, &block) 
    end 
end 

x = Test.new 
puts x.x.inspect # "method func", member overrides global 
puts x.y.inspect # "global func 2", "method_missing" was not called 
puts x.z.inspect # "Math" module, member did not override global 
puts x.w.inspect # ["method_func", "local_var"], local variables are always considered before anything else 
+0

Sie können keine Methoden mit Großbuchstaben definieren, oder? Es wird keinen Fehler bei der Definition auslösen, aber wenn Sie versuchen, es aufzurufen (versuchen Sie 'def Something' anstelle eines bereits definierten Moduls/Klasse) – Ninigi

+0

@Ninigi: Sie können Methoden mit Großbuchstaben definieren, siehe' Array() 'in [ 'Kernel'] (https://ruby-doc.org/core-2.3.1/Kernel.html) – spickermann

+0

@spickermann uh, naja die Quelle ist C Code, aber nicht Ruby ... Es ist nicht' Def Array'. Um etwas klarer zu sein mit dem, was ich meinte: Sie können die Methode DEFINIEREN, aber nicht aufrufen, weil Ruby immer davon ausgeht, dass es eine Konstante ist, keine Methode. – Ninigi

Antwort

4

Rubys Methode Lookup-Algorithmus ist eigentlich wirklich einfach:

  • den class Zeiger des
  • Empfänger abgerufen werden, wenn die Methode ist, rufen Sie es
  • sonst die superclass Zeiger abrufen und wiederholen

Das war's.

Wenn der Algorithmus zu einem Punkt kommt, an dem es keine Superklasse mehr gibt, aber die Methode noch nicht gefunden hat, wird der gesamte Prozess erneut gestartet, wobei method_missing als Nachricht und der Name der ursprünglichen Nachricht vorangestellt wird zu den Argumenten. Aber das ist es. Das ist der ganze Algorithmus. Es ist sehr klein und sehr einfach, und es hat, um sehr klein und sehr einfach zu sein, weil Methodensuche die am häufigsten durchgeführte Operation in einer objektorientierten Sprache ist.

Hinweis: Ich ignoriere vollständig Module#prepend/Module#prepend_features, da ich einfach nicht genug darüber weiß, wie es funktioniert. Ich weiß nur, was es tut, und das ist gut genug für mich.

Beachten Sie auch: Ich ignoriere Leistungsoptimierungen wie das Zwischenspeichern des Ergebnisses einer Methodensuche in etwas wie einem polymorphen Inline-Cache.

Okay, aber hier ist der Trick: Wo genau zeigen die Zeiger class und superclass auf? Nun, sie tun nicht zeigen, was die Object#class und Class#superclass Methoden zurückgeben. Also, lasst uns ein wenig zurücktreten.

Jedes Objekt verfügt über einen Zeiger class, der auf die Klasse des Objekts zeigt. Und jede Klasse hat einen Zeiger , der auf seine Oberklasse verweist.

Lassen sich ein laufendes Beispiel starten: Jetzt

class Foo; end 

, haben wir Klasse Foo und seine superclass Zeiger zeigen auf Object.

foo = Foo.new 

Und unser Objekt foo ‚s class Zeiger auf Foo.

def foo.bar; end 

Jetzt beginnt die Dinge interessant zu werden. Wir haben eine Singleton-Methode erstellt. Nun, eigentlich gibt es so etwas wie eine Singleton-Methode nicht, es ist wirklich nur eine normale Methode in der Singleton-Klasse. Also, wie funktioniert das? Nun, jetzt zeigt der class Zeiger auf foo Singleton Klasse und foo Singleton Klasse superclass Zeiger auf Foo! Mit anderen Worten, die Singleton-Klasse wurde zwischen foo und ihrer "echten" Klasse Foo eingefügt.

Wenn wir jedoch foo über seine Klasse fragen, antwortet er immer noch Foo:

foo.class #=> Foo 

Die Object#class Methode kennt Singleton-Klassen und einfach überspringt sie, nach dem superclass Zeiger, bis sie ein „normales finden "Klasse, und gibt das zurück.

nächste Komplikation:

module Bar; end 

class Foo 
    include Bar 
end 

Was hier passiert? Ruby erstellt eine neue Klasse (nennen wir es Barʹ), genannt enthalten Klasse. Der Methodentabellenzeiger, der Klassenvariablentabellenzeiger und der Konstanten-Tabellenzeiger dieser Klasse verweisen auf die Methodentabelle, die Klassenvariablentabelle und die Konstantentabelle von Bar. Dann macht Ruby Barʹ 's superclass Zeiger auf Foo' s aktuelle Superklasse und macht Foo 's Superklasse Zeiger auf Barʹ. Mit anderen Worten, das Einschließen eines Moduls erstellt eine neue Klasse, die als Oberklasse der Klasse eingefügt wird, in der das Modul enthalten ist.

Es gibt eine kleine Komplikation hier: Sie können auch include Module in Module. Wie funktioniert das? Nun, Ruby verfolgt einfach die Module, die in einem Modul enthalten waren. Und dann, wenn das Modul in einer Klasse enthalten ist, wird es rekursiv die oben genannten Schritte für jedes enthaltene Modul wiederholen.

Und das ist alles, was Sie brauchen, um über die Ruby-Methode Lookup wissen:

  • die Klasse finden
  • folgen die Oberklasse
  • Singletons Klassen oben einfügen Objekte
  • umfassen Klassen über den Klassen einfügen

Nun schauen wir uns einige Ihrer Fragen an:

Beim expliziten Aufrufen von Methoden für eine Klasse gibt es viele Abbildungen in der Reihenfolge, in der die von ihnen eingeschlossenen Klassen und Module gesucht werden (und somit genau das, was super in jedem Fall aufruft). Wenn jedoch nicht explizit eine Methode aufgerufen wird, z.B. eine Ebene func args anstatt self.func args Was ist die Suchreihenfolge?

Das Gleiche. self ist der implizite Empfänger. Wenn Sie keinen Empfänger angeben, lautet der Empfänger self. Und Klammern sind optional. Mit anderen Worten:

func args 

ist genau die gleichen wie

self.func(args) 

Warum in meinem Beispiel unten ist, wobei das Element Methode func findet das Element Verfahren vor dem globalen Aufruf, sondern func2 Funde die globale ohne method_missing aufgerufen wird?

In Ruby gibt es keine "globale Methode". Es gibt auch keine "Mitgliedsmethode". Jede Methode ist eine Instanzmethode. Zeitraum. Es gibt keine globalen, statischen Klassen-, Singleton-, Member-Methoden, Prozeduren, Funktionen oder Subroutinen.

Eine auf der obersten Ebene definierte Methode wird zu einer private Instanzmethode der Klasse Object. Test erbt von Object. Führen Sie die Schritte, die ich oben beschrieben, und Sie finden genau, was los ist:

  • abrufen x ‚s Klassenzeiger: Test
  • Hat Test eine Methode namens func: Ja, so rufen Sie es.

Jetzt wieder:

  • abrufen x ‚s Klassenzeiger: Test
  • hat Test ein Verfahren func2 genannt: Nein!
  • abrufen Test ‚s Super Zeiger: Object
  • Does Object ein Verfahren func2 genannt haben: Ja, so rufen Sie es.

Und wenn die globalen stattdessen ein Modul/Klasse/Typ ist, warum ist das Element mit dem gleichen Namen überhaupt nicht gefunden?

Auch hier gibt es kein global, hier sind keine Mitglieder. Dies hat auch nichts mit Modulen oder Klassen zu tun. Und Ruby hat keine (statischen) Typen.

Math 

ist ein Verweis auf eine Konstante. Wenn Sie eine Methode mit demselben Namen aufrufen möchten, müssen Sie sicherstellen, dass Ruby erkennt, dass es sich um eine Methode handelt. Es gibt zwei Dinge, die nur Methoden haben können: einen Empfänger und Argumente. So können Sie entweder fügen Sie einen Empfänger:

self.Math 

oder Argumente:

Math() 

und jetzt Rubin weiß, dass Sie die Methode Math und nicht die Konstante Math bedeuten.

Das gleiche gilt übrigens für lokale Variablen. Und für Setter. Wenn Sie einen Setter aufrufen möchten, statt eine lokale Variable zuzuweisen, müssen Sie sagen:

self.func = 'setter method' 
+0

'def x; self.func2 end' nennt 'method_missing' und' def x; func2 end 'nicht, also gibt es einen deutlichen Unterschied. Und wenn es privat wäre, würde "Func2" nicht verboten sein? –

+0

'func2' ist privat, private Methoden können nur ohne expliziten Empfänger aufgerufen werden. In 'self.func2' wird' Objekt # func2' nicht gefunden, da es privat ist, daher wird 'method_missing' aufgerufen. Ich bekomme Ihre zweite Frage nicht: Warum sollte das Anrufen privater Methoden verboten werden? Was wäre der Sinn privater Methoden, wenn das Anrufen nicht erlaubt wäre? –

+0

Zunächst einmal, Kudos zu dieser Antwort @ JörgWMittag Ich denke, es ist eine nette Lektüre für Anfänger und Fortgeschrittene wie mich. – Ninigi