ruby-mine

exploring the mine

Optionen aufladen mit Ruby

von murphy am 16.05.2006 (02 Uhr)

Für why the lucky stiff ist die Sprache Ruby keine Konstante (nicht einmal ein Eigenvektor), sondern ein Spielzeug. Das sieht man an seinem jüngsten Blogeintrag auf RedHanded in der Kategorie bits, mit dem schönen Namen EigenCharges. Die Leser waren wie üblich begeistert bis schockiert, unter anderem cypher, der auch diesen Beitrag angestoßen hat. Zeit für den Syntax-Sonntag! Im Gegensatz zu Superman kommen wir allerdings nicht immer pünktlich.

Ruby-DSL of the Week - oder - Was macht why da schon wieder?

Why schreibt folgendes:

class RubyTalk < Filter
  + to("ruby-talk@ruby-lang.org")
  + cc("ruby-talk@ruby-lang.org")
  - from("common.troll@gifted.net")
end

Da stehen der kleinen Japanerin die roten Haare zu Berge! Sieht ein wenig aus wie eine UML-Klasse. Ist aber legaler Ruby-Code. Soll vermutlich heißen: RubyTalk bekommt Standardoptionen, nämlich zwei Empfänger und einen blockierten Troll. Das Neue hierbei ist, wie die Vorzeichen (Ladungen, Charges) genutzt werden, um den Optionen zusätzliche Aussagekraft zu verleihen: Mit cc wird ein Empfänger hinzugefügt, und der arme Troll wird über das from-Feld identifiziert und nicht mehr beachtet. Ein positiv geladenes from-Feld könnte hingegen einfach den Absender definieren.

Keine Angst, dahinter steckt noch keine Funktionalität. Mit why's Code kann man rein gar nichts mailen oder posten. Es ist nur eine syntaktische Spielerei. Frizzeln wir sie doch mal auseinander:

+ to("ruby-talk@ruby-lang.org") bewirkt folgendes:

Filter.to("ruby-talk@ruby-lang.org").+@()

Zu dem ominösen +@ komme ich gleich noch.

Metaprogrammierung, 1x1

Da die Methoden für die einzelnen Optionen sehr ähnlich sind, hat why sie einfach dynamisch erzeugt. Der folgende Code ist total simples Meta-Ruby, wie es einem auf RedHanded wöchentlich über den Weg läuft:

class Filter
  class << self
    attr_accessor :rules
    [:from, :to, :cc, :subject].each do |m|
      define_method(m) do |*a|
        r = Rule.new(m, *a)
        (@rules ||= []) << r
        r
      end
    end
  end
end

Alles klar? Wenn nicht: Seeing Metaclasses Clearly ist eine gute Einführung in Rubys metamagische Fähigkeiten. Beschäftigt euch damit! Es macht wirklich Spaß, mit Ruby zu zaubern. Für diesen Artikel soll es reichen, dass für jedes der vier Symbole so eine Methode angelegt wird:

class Filter
  def Filter.from(*a)
    r = Rule.new(:from, *a)
    (@rules ||= []) << r
    r
  end
end

Ein Aufruf von to erzeugt also ein neues Rule-Objekt. Die erzeugten Regeln werden in @rules gespeichert - einer Klasseninstanzvariable. Sieht aus wie eine normale Instanzvariable? Ist auch eine. Sie gehört zu der Klasse, in der to aufgerufen wird, denn Klassen sind auch nur Objekte. Zwischenergebnis: to liefert eine Instanz von Filter::Rule.

@? Also doch Emails!

Nein. +@ ist ein interner Name für den unären +-Operator. Unär heißt, dass der Operator nur einen Operanden hat. Das ist gar nichts besonderes: Das unäre Vorzeichen +1993 bedeutet eben etwas anderes als die binäre Addition 17 + 4. Beides sind aber nur Funktionen: Man könnte auch neg(1993) oder add(17, 4) schreiben. Ruby unterscheidet diese beiden Methoden ebenfalls und nennt sie +@ (unär) und + (binär). Beim Minuszeichen analog -@ und -. Beim Einlesen des Ruby-Programms wird entschieden, ob solch ein mehrdeutiger Operator unär oder binär gemeint ist. Hier ist es eindeutig die +@-Methode:

+ cc("ruby-talk@ruby-lang.org")

Wie gesagt, Ruby unterscheidet im Allgemeinen anhand der Verwendung, welche Methode sie aufruft. Die internen Namen benutzt man, wenn man die Methoden überschreiben möchte, wie why es hier getan hat:

class Filter
  class Rule
    attr_accessor :field, :terms, :action
    def initialize(f, *a)
      @field = f
      @terms = a
    end
    def -@; @action = :reject end
    def +@; @action = :accept end
  end
end

Schön, die Filter-Klasse enthält also noch eine Hilfsklasse namens Rule. Wie wir bereits wissen, liefert uns ein Aufruf von Filter.to() so ein Regel-Objekt. Die Instanzvariablen des Objektes werden auf verschiedene Weise gesetzt:

Der Aufruf + to("ruby-talk@ruby-lang.org") erzeugt also eine Instanz von Filter::Rule mit:

Wozu das Ganze?

Freiheit. Schönheit. Einfachheit. Vereinfachung der Sprache für den Anwender. Man könnte auch andere Varianten implementieren:

class RubyMine < Filter2
  from << "superman@townsville.sw"
  + tag(:super)
  --- indent
  - /VIAGRA/
  # insert freaky new idea here
end

Warnung vor Warnungen

why's neue Sprache hat einen Schönheitsfehler: Aufrufe von unären Methoden, deren Ergebnis keiner Variablen zugewiesen wird, erzeugen Warnungen:

warning: useless use of +@ in void context

Das macht diese Lösung proplematisch bis unbrauchbar. Ob Das Ruby-Core-Team daran gedacht hat, als es die Warnung implementierte? Ich habe sie jedenfalls bisher nur dann gesehen, wenn ich sie nicht haben wollte.

Weg damit! Mehr Freiraum für DSLs!

Weiterlesen


Kommentar schreiben

Name (notwendig)

Mail (wird nicht veröffentlicht)

Webseite