ruby-mine

exploring the mine

13 Wege um ein Ruby-Symbol zu betrachten

von cypher am 03.03.2007 (16 Uhr)

(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:

  1. der Name von Irgendetwas, nicht nur ein Haufen Text
  2. ein Label in einer freien Enumeration
  3. ein konstanter, eindeutiger Name
  4. ein "interner" String
  5. ein Objekt mit O(1) Vergleich
  6. ein Lisp Bezeichner
  7. ein Ruby Bezeichner
  8. ein Keyword für ein Keyword-Argument
  9. eine ausgezeichnete Wahl für einen Hash-Key
  10. ähnlich zu einem Mac OSType
  11. ein Speicherleck
  12. ein geschickter Weg um nur eine einzelne Kopie eines Strings zu speichern
  13. ein C typedef names "ID"

1. Ein Ruby Symbol ist der Name von Irgendetwas, nicht nur ein Haufen Text

In Ruby werden Symbole üblicherweise verwendet um die Dinge beim Namen zu nennen:
find_speech( :gettysburg_address )
Aber um grössere Texte darzustellen, werden Strings verwendet:
"Four score and seven years ago..."

2. Ein Ruby Symbol ist ein Label in einer freien Enumeration

In C++ (und vielen anderen Sprachen) können Enumerationen verwendet werden, um Beziehungen zwischen Konstanten darzustellen:
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

3. Ein Ruby Symbol ist ein konstanter, eindeutiger Name

In Ruby kann der Inhalt eines Strings verwändert werden:
"foo"[0] = ?b # "boo"
Der Inhalt eines Symbols kann dagegen nicht verändert werden:
:foo[0]  = ?b # Verursacht einen Fehler
Gleichermassen können in Ruby verschiedene String-Objekte mit gleichem Inhalt erzeugt werden:
# Gleiche String-Inhalte, verschiedene Strings
"open".object_id != "open".object_id
Aber zwei Symbole mit dem gleichen Namen sind immer das selbe Objekt:
# Gleicher Symbolname, gleiches Objekt.
:open.object_id == :open.object_id

4. Ein Ruby Symbol ist ein "interner" String

In Ruby kann ein String mittels intern in ein Symbol verwandelt werden:
"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

5. Ein Ruby Symbol ist ein Objekt mit O(1)-Vergleich

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

6. Ein Ruby Symbol ist ein Lisp Bezeichner

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.

7. Ein Ruby Symbol ist ein Ruby Bezeichner

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

8. Ein Ruby Symbol ist das Keyword für ein Keyword-Argument

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

9. Ein Ruby Symbol ist eine ausgezeichnete Wahl für einen Hash-Key

Symbole werden üblicherweise als Schlüssel für Hash-Tabellen verwendet:

options = {}
options[:auto_save]     = true
options[:show_comments] = false

10. Ein Ruby Symbol ist ähnlich zu einem Mac OSType

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

11. Ein Ruby Symbol ist ein Speicherleck

Aufgrund der Art und Weise, mit der Ruby Symbole gespeichert werden, können sie niemals vom Garbage Collector eingesammelt werden. Wenn also 10.000 Symbole erzeugt werden, ist dieser Speicher für das Programm nicht mehr anderweitig einsetzbar.

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.

12. Ein Ruby Symbol ist ein geschickter Weg um nur eine einzelne Kopie eines Strings zu speichern

(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.

13. Ein Ruby Symbol ist ein C typedef names "ID"

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);

Andere Erklärungen von Ruby Symbolen

Falls keine dieser Erklärungen ausreicht, helfen vielleicht folgende Links weiter (allesamt auf Englisch):
  1. Symbols Are Not Immutable Strings
  2. Using Symbols for the Wrong Reasons
  3. Yet Another Blog About Ruby Symbols
  4. Digging into Ruby Symbols
  5. Understanding Ruby Symbols


Kommentare

  1. Johannes schrieb am 03.03.2007 (19 Uhr)

    ;; Retourniert '(defun double (x) (* x 2) Da fehlt aber noch eine schließende Klammer, oder?

  2. Murphy schrieb am 05.03.2007 (21 Uhr)

    super gemacht, cypher! eine tolle idee, ausgewählte artikel ins deutsche zu übersetzen. sehr interessanter beitrag, und perfekt für die Ruby-Mine. danke!

  3. Jonas schrieb am 31.12.2009 (17 Uhr)

    Jetzt erst verstehe ich die Bedeutung von Ruby-Symbolen. Danke für die Übersetzung.