(Dieser Artikel ist die deutsche Übersetzung von 13 Ways of Looking at a Ruby Symbol, welche mit freundlicher Erlaubnis des Autors erfolgt)
Neue Ruby-Programmierer fragen sich oft “Was, genau, ist eigentlich ein Symbol? Und wie unterscheidet es sich von einem String?” Keine einzelne Antwort arbeitet für jeden, deshalb – in Anlehnung an Wallace Stevens – hier 13 Wege um ein Ruby-Symbol zu betrachten.
Ein Ruby-Symbol ist:
find_speech( :gettysburg_address )Aber um grössere Texte darzustellen, werden Strings verwendet:
"Four score and seven years ago..."
enum BugStatus { OPEN, CLOSED };
BugStatus original_status = OPEN;
BugStatus current_status = CLOSED;
Aber da Ruby eine dynamische Sprache ist, halten wir uns weder mit dem deklarieren eines BugStatus-Typs auf noch mit dem Verwalten der gültigen Werte für die Konstanten. Stattdessen repräsentieren wir die Enumeration mit Symbolen:
original_status = :open current_status = :closed
"foo"[0] = ?b # "boo"Der Inhalt eines Symbols kann dagegen nicht verändert werden:
:foo[0] = ?b # Verursacht einen FehlerGleichermassen können in Ruby verschiedene String-Objekte mit gleichem Inhalt erzeugt werden:
# Gleiche String-Inhalte, verschiedene Strings "open".object_id != "open".object_idAber zwei Symbole mit dem gleichen Namen sind immer das selbe Objekt:
# Gleicher Symbolname, gleiches Objekt. :open.object_id == :open.object_id
"foo".intern # Gibt :foo zurück
intern verwaltet eine Hash-Tabelle, welche Strings mit ihrem korrespondierenden Symbol verknüpft. Wenn intern einen String zum ersten Mal sieht, erzeugt es ein neues Symbol und speichert es in der Hash-Tabelle. Wenn intern das nächste mal diesen String sieht, ruft es das bereits erzeugte Objekt aus der Hash-Tabelle ab.
Eine mögliche Implementation von Symbol und intern könnte folgendermassen aussehen:class MySymbol TABLE={} def initialize(str) @str = str end def to_s() @str end def ==(other) self.object_id == other.object_id end end class String def my_intern table = MySymbol::TABLE unless table.has_key?(self) table[self] = MySymbol.new(self) end table[self] end end "foo".my_intern
Um zwei Strings zu vergleichen muss man unter Umständen jeden Buchstaben einzeln ansehen. Bei zwei Strings der Länge N wird dieser Vorgang N+1 Vergleiche benötigen (was in der Infomatik als "O(N) Laufzeit" bezeichnet wird).
def string_comp str1, str2 return false if str1.length != str2.length for i in 0...str1.length return false if str1[i] != str2[i] end return true end string_comp "foo", "foo"
def symbol_comp sym1, sym2 sym1.object_id == sym2.object_id end symbol_comp :foo, :foo
Nachdem aber jedes Vorkommen von :foo sich auf das gleiche Objekt bezieht, kann man Symbole über Objekt-IDs vergleichen. Dies kann mit einem einzelnen Vergleich bewerkstelligt werden (was in der Infomatik als "O(1) Laufzeit" bezeichnet wird).
def symbol_comp sym1, sym2 sym1.object_id == sym2.object_id end symbol_comp :foo, :foo
Die ältesten Vorfahren der Ruby-Symbole sind Lisp-Symbole. In Lisp werden Symbole verwendet um Bezeichner (Variablen und Funktionsnamen) in einem geparstem Programm darzustellen. Angenommen wir haben eine Datei namens double.l, welche eine einzelne Funktion enthält:
(defun double (x) (* x 2))
Diese Datei kann mittels read geparst werden:
(read "double.l") ;; Retourniert '(defun double (x) (* x 2))
Dies gibt eine Liste zurück die folgende Symbole enthält: defun, double, *, x (zweimal) und die Nummer 2.
In Ruby können Bezeichner (Variablen, Funktionen und Konstanten) ermittelt werden, wärend das Programm läuft. Dies wird typischerweise mit Symbolen gemacht.
class Demo # Was wir herausfinden wollen DEFAULT = "Hello" def initialize @message = DEFAULT end def say() @message end # Symbole verwenden, um die Bezeichner herauszufinden def look_up_with_symbols [Demo.const_get(:DEFAULT), method(:say), instance_variable_get(:@message)] end end Demo.new.look_up_with_symbols
Wenn Keyword-Argumente einer Ruby-Methode übergeben werden, werden die Keywords mit Symbolen festgelegt:
# Aufbauen einer URL für 'bug' in Rails. url_for :controller => 'bug', :action => 'show', :id => bug.id
Symbole werden üblicherweise als Schlüssel für Hash-Tabellen verwendet:
options = {} options[:auto_save] = true options[:show_comments] = false
MacOS verwendet 4-Buchstaben-Abkürzungen um offene Enumerationen darzustellen:
enum {
kSystemFolderType = 'macs',
kDesktopFolderType = 'desk',
// ...und so weiter...
kTrashFolderType = 'trsh'
};
OSType folder = kSystemFolderType;
In Ruby erfüllen Symbole den gleichen Zweck:
:system_folder :desktop_folder :trash_folder
Einige Scheme Implementationen verwenden eine schlaue Art von intern, welche eine sogenannte schwache Hash-Tabelle verwendet um Symbole abzuspeichern. Diese Vorgehensweise erlaubt es dass Symbole vom Garbage Collector eingesammelt werden können, ohne ihre einzigartigen Eigenschaften zu verlieren.
(Für eine ähnliche Idee siehe diesen Artikel.)
Angenommen wir arbeiten an einem Parser für natürliche Sprachen um Frühstückbestellungen verstehen zu können. Dazu haben wir eine Sammlung aus 30.000 Sätzen welche echte Frühstückbestellungen repräsentieren, und wir versuchen ein Muster zu erkennen.
Aber obwohl wir so viele Sätze haben ist das eigentliche Vokabular ziemlich beschränkt. 15.000 Kopien des Wortes "Speck" abzuspeichern macht nicht viel Sinn! Stattdessen verwenden wir Symbole um die einzelnen Wörter darzustellen:
corpus = [ [:i, :want, :some, :bacon], [:i, :want, :some, :eggs], [:give, :me, :some, :bacon], [:chunky, :bacon], # ... 29,995 weitere Phrasen ... [:some, :toast, :please] ]
In den Anfangstagen der KI-Forschung wurde diese Strategie von vielen Lisp-Programmen verwendet um Text zu repräsentieren.
Intern verwendet Ruby 1.8 den Typ ID um Symbole darzustellen, welcher ein Typedef für einen Integer ohne Vorzeichen ist. Eine ID stellt dabei Eintrag in Ruby™s Symbol-Tabelle dar.
typedef unsigned long ID;
Einige interessante Funktionen, die mit Symbolen verbunden sind:
// Fügt einen C-String in die Symbol-Tabelle ein ID rb_intern(const char *name); // Konvertiert eine ID zu einem Symbol-Objekt #define ID2SYM(x) // Konvertiert einen String zu einem Symbol-Objekt VALUE rb_str_intern(VALUE s);
Kommentare
;; Retourniert '(defun double (x) (* x 2) Da fehlt aber noch eine schließende Klammer, oder?
super gemacht, cypher! eine tolle idee, ausgewählte artikel ins deutsche zu übersetzen. sehr interessanter beitrag, und perfekt für die Ruby-Mine. danke!
Jetzt erst verstehe ich die Bedeutung von Ruby-Symbolen. Danke für die Übersetzung.