Programmierung in Ruby

Der Leitfaden der Pragmatischen Programmierer

Reflektion, Objekt-Raum und Verteiltes Ruby



Einer der vielen Vorteile einer dynamischen Sprache wie Ruby ist die Fähigkeit der Introspektion --- die Untersuchung von Aspekten des Programms aus dem Innern dieses Programms selber heraus. Bei Java zum Beispiel nennt man diese Fähigkeit Reflektion.

Das Wort ``Reflektion'' beschwört das Bild herauf, wie man sich selber im Spiegel ansieht --- vielleicht um das unaufhaltsame Vordringen diese kahlen Stellen auf der Kopfhaut zu untersuchen. Das ist ein treffender Vergleich: wir untersuchen Teile unseres Programms, die normalerweise von unserem Standpunkt aus nicht sichtbar sind.

Wenn wir denn in tiefer Kontemplation Räucherstäbchen abbrennen und unseren Nabel betrachten (und darauf achten, beides nicht zusammenzubringen), was können wir dabei über unser Programm lernen? Wir entdecken folgendes:

Mit diesen Informationen bewaffnet können wir uns bestimmte Objekte herauspicken und entscheiden, welche ihrer Methoden wir zur Laufzeit aufrufen --- selbst wenn die Klasse des Objekts beim Schreiben des Codes noch nicht mal existiert hat. Wir könnten sogar anfangen, furchtbar schlaue Dinge zu machen, vielleicht sogar das Programm selber während der Laufzeit abändern.

Hört sich das furchteinflößend an? Das muss es nicht. Tatsächlich lassen uns diese Reflektionsmöglichkeiten einige sehr nützliche Dinge anstellen. Weiter unten in diesem Kapitel sehen wir uns verteiltes Ruby und Marschaling an, zwei auf der Reflektion basierende Techniken, die uns ermöglichen, Objekte rund um die Welt und quer durch die Zeit zu schicken.

Betrachtung von Objekten

Wer hat sich nicht schon danach gesehnt, alle lebenden Objekte eines Programms durchlaufen zu können. Wir ganz bestimmt! In Ruby führt man diesen Trick aus mit ObjectSpace::each_object. Damit kann man allerlei hübsche Tricks anstellen.

Wenn man zum Beispiel über alle Objekte des Typs Numeric iterieren möchte, schreibt man das folgende.

a = 102.7
b = 95.1
ObjectSpace.each_object(Numeric) {|x| p x }
erzeugt:
95.1
102.7
2.718281828
3.141592654

Hoppla, wo kommen denn die beiden letzten Zahlen her? Wir haben sie in unserem Programm nicht definiert. Aber auf Seite 433 findet man im Math-Modul die Definitionen für e und PI; und weil wir schließlich alle lebenden Objekte unseres Systems sehen wollten, tauchen diese auch auf.

Es gibt da allerdings eine Falle. Wiederholen wir das gleiche Beispiel mit andern Zahlen.

a = 102
b = 95
ObjectSpace.each_object(Numeric) {|x| p x }
erzeugt:
2.718281828
3.141592654

Keines der beiden Fixnum-Objekte, die wir erzeugt haben, taucht auf. Das kommt daher, dass ObjectSpace keine Objekte mit unmittelbaren Werten kennt: Fixnum, true, false und nil.

Betrachtung des Inneren von Objekten

Wenn man erst einmal ein interessantes Objekt gefunden hat, könnte man sich versucht fühlen, seine Eigenschaften herauszufinden. Anders als bei statischen Sprachen, bei denen der Typ die Klasse bestimmt und diese wiederum die unterstützten Methoden, sind Objekte in Ruby freigegebenen. Man kann nie genau sagen, was ein Objekt alles kann, solange man ihm nicht unter den Hut gesehen hat. [Oder unter die Schirmmütze, bei Objekten, die westlich des Atlantik erzeugt wurden.]

Als Beispiel holen wir uns eine Liste aller Methoden, auf die ein Objekt reagiert.

r = 1..10 # erzeugt ein Bereichs-Objekt
list = r.methods
list.length » 60
list[0..3] » ["size", "length", "exclude_end?", "inspect"]

Oder wir können prüfen, ob ein Objekt eine bestimmte Methode unterstützt.

r.respond_to?("frozen?") » true
r.respond_to?("hasKey") » false
"me".respond_to?("==") » true

Wir können die Klasse unseres Objekts bestimmen und seine einzigartige Objekt-Id und wir können seine Beziehung zu anderen Klassen untersuchen.

num = 1
num.id » 3
num.class » Fixnum
num.kind_of? Fixnum » true
num.kind_of? Numeric » true
num.instance_of? Fixnum » true
num.instance_of? Numeric » false

Betrachtung von Klassen

Das Wissen über Objekte ist die eine Seite der Reflektion, aber um alles zu erfassen, muss man sich auch die Klassen ansehen können --- ihre Methoden und ihre Konstanten.

Die Klassenhierarchie zu untersuchen, ist einfach. Man erhält den Vorfahr einer speziellen Klasse über Class#superclass. Bei Klassen und Modulen zeigt Module#ancestors sowohl Superklassen als auch eingebundene (mixed-in) Module.

klass = Fixnum
begin
  print klass
  klass = klass.superclass
  print " < " if klass
end while klass
puts
p Fixnum.ancestors
erzeugt:
Fixnum < Integer < Numeric < Object
[Fixnum, Integer, Precision, Numeric, Comparable, Object, Kernel]

Wenn man die komplette Klassenhierarchie haben möchte, lässt man einfach diesen Code für jede Klasse im System laufen. Wir können ObjectSpace nutzen, um über alle Class-Objekte zu iterieren:

ObjectSpace.each_object(Class) do |aClass|
   # ...
end

Betrachtung des Inneren von Klassen

Wir können noch etwas mehr über die Methoden und Konstanten eines speziellen Objekts herausfinden. Statt nur zu prüfen, ob das Objekt auf eine gegebene Meldung reagiert, können wir die Methoden nach Zugriffs-Leveln untersuchen, wir konnen nach Singleton-Methoden forschen und wir können uns die Konstanten dieses Objekts ansehen.

class Demo
  private
    def privMethod
    end
  protected
    def protMethod
    end
  public
    def pubMethod
    end
  def Demo.classMethod
  end
  CONST = 1.23
end
Demo.private_instance_methods » ["privMethod"]
Demo.protected_instance_methods » ["protMethod"]
Demo.public_instance_methods » ["pubMethod"]
Demo.singleton_methods » ["classMethod"]
Demo.constants - Demo.superclass.constants » ["CONST"]

Module.constants gibt alle über dieses Modul erhältliche Konstanten zurück, auch die aus den Super-Klassen. An denen sind wir im Moment nicht interessiert, also entfernen wir sei aus unserer Liste.

Hat man jetzt eine solche Liste mit Methoden-Namen, möchte man sie auch aufrufen. In Ruby ist das glücklicherweise ganz einfach.

Dynamischer Aufruf von Methoden

C- und Java-Programmierer schreiben oft eine Art Verteiler-Tabelle: Funktionen, die basierend auf einem Kommando aufgerufen werden sollen. Man denke an eine typische Spracheigenheit von C, wo man einen String in einen Funktionspointer übersetzen muss:

typedef struct {
  char *name;
  void (*fptr)();
} Tuple;

Tuple list[]= {   { "play",   fptr_play },   { "stop",   fptr_stop },   { "record", fptr_record },   { 0, 0 }, };

...

void dispatch(char *cmd) {   int i = 0;   for (; list[i].name; i++) {     if (strncmp(list[i].name,cmd,strlen(cmd)) == 0) {       list[i].fptr();       return;     }   }   /* not found */ }

In Ruby macht man das ganze in einer Zeile. Man steckt alle Kommando-Funktionen in eine Klasse, erzeugt eine Instanz dieser Klasse (wir nennen sie hier commands)und bitte das Objekt um Ausführung der Methode mit demselben Namen wie der Kommando-String.

commands.send(commandString)

Und überhaupt macht das noch viel mehr als die C-Version --- es ist dynamisch. Die Ruby-Version findet genauso leicht neue, zur Laufzeit hinzugefügte Methoden.

Man muss keine spezielle command-Klasse für send schrieben: das funktioniert mit jedem Objekt.

"John Coltrane".send(:length) » 13
"Miles Davis".send("sub", /iles/, '.') » "M. Davis"

Bei einer anderen Art des dynamischen Aufrufs von Methoden benutzt man Method-Objekte. Ein Method-Objekt ist ähnlich wie ein Proc-Objekt: Es repräsentiert ein Stückchen Code und einen Kontext, in dem er ausgeführt wird. In diesem Fall ist der Code der Rumpf der Methode und der Kontext ist das Objekt, das die Methode erzeugt hat. Wenn man so ein Method-Objekt erst mal hat, kann man es später mit der Meldung call ausführen.

trane = "John Coltrane".method(:length)
miles = "Miles Davis".method("sub")
trane.call » 13
miles.call(/iles/, '.') » "M. Davis"

Man kann das Method-Objekt nun in der Gegend herumschicken wie jedes andere Objekt auch, und wenn man Method#call aufruft, wird die Methode ausgeführt, als hätte man sie mit dem ursprünglichen Objekt aufgerufen. Es ist, als hätte man wie in C einen Function-Pointer, aber jetzt in völlig objekt-orientierter Weise.

Man kann Method-Objekte auch mit Iteratoren benutzen.

def double(a)
  2*a
end
mObj = method(:double)
[ 1, 3, 5, 7 ].collect(&mObj) » [2, 6, 10, 14]

Aller guten Dinge sind drei, und hier ist noch eine Möglichkeit, Methoden dynamisch aufzurufen. Die eval-Methode (und ihre Variationen wie class_eval, module_eval und instance_eval) liest einen willkürlichen String mit legalem Ruby-Quell-Code und führt ihn aus.

trane = %q{"John Coltrane".length}
miles = %q{"Miles Davis".sub(/iles/, '.')}
eval trane » 13
eval miles » "M. Davis"

Wenn man eval benutzt, kann es hilfreich sein, explizit den Kontext anzugeben, in dem der Ausdruck ausgewertet werden soll, statt einfach nur den aktuellen Kontext zu nehmen. Man kann einen solchen Kontext erhalten, indem man Kernel#binding an der gewünschten Stelle aufruft.

class CoinSlot
  def initialize(amt=Cents.new(25))
    @amt = amt
    $here = binding
  end
end

a = CoinSlot.new eval "puts @amt", $here eval "puts @amt"
erzeugt:
$0.25USD
nil

Das erste eval wertet @amt aus im Kontext der Instanz der Klasse CoinSlot. Das zweite eval wertet @amt aus im Kontext von Object, wobei die Instanz-Variable @amt nicht definiert ist.

Performance-Überlegungen

Wir haben in diesem Abschnitt verschiedene Möglichkeiten kennengelernt, frei gewählte Methoden eines Objekts aufzurufen: Object#send, Method#call und die verschiedenen Varianten von eval.

Man kann jetzt eine dieser Methoden je nach Bedarf bevorzugen, aber man sollte sich darüber im Klaren sein, dass eval deutlich langsamer als die anderen ist (oder, für die Optimisten unter uns, send und call sind deutlich schneller als eval).

require "benchmark"   # from the Ruby Application Archive
include Benchmark

test = "Stormy Weather" m = test.method(:length) n = 100000

bm(12) {|x|   x.report("call") { n.times { m.call } }   x.report("send") { n.times { test.send(:length) } }   x.report("eval") { n.times { eval "test.length" } } }
erzeugt:
                  user     system      total        real
call          0.270000   0.000000   0.270000 (  0.243899)
send          0.250000   0.000000   0.250000 (  0.227987)
eval          2.990000   0.050000   3.040000 (  2.929909)

System-Hooks

Ein hook ist eine Technik, mit der man Ruby-Ereignisse abfangen kann, etwa die Erzeugung eines Objekts.

Die einfachste Hook-Technik ist das Abfangen von Methoden-Aufrufen in System-Klassen. Vielleicht möchte man alle Betriebssystem-Kommandos, die das Programm ausführt, mitprotokollieren. Man nennt einfach die Methode Kernel::system um [Diese von Eiffel inspirierte Fähigkeit, bei der man eine Eigenschaft umbenennt und neu definiert, ist sehr nützlich, aber man sollte auf die Gefahren achten. Wenn eine Unterklasse das selbe macht und die Methoden wieder mit den selben Namen umbenennt, erhält man eine Endlos-Schleife. Das kann man vermeiden mit einem Alias der Methode auf einen eindeutigen Namen oder durch die Benutzung einer konsistenten Namens-Konvention.] und ersetzt sie durch eine eigene, die sowohl das Kommando protokolliert als auch die ursprüngliche Kernel-Methode aufruft.

module Kernel
  alias_method :old_system, :system
  def system(*args)
    result = old_system(*args)
    puts "system(#{args.join(', ')}) returned #{result}"
    result
  end
end

system("date") system("kangaroo", "-hop 10", "skippy")
erzeugt:
Sun Mar  4 23:25:46 CST 2001
system(date) returned true
system(kangaroo, -hop 10, skippy) returned false

Ein leistungsfähigerer Hook ist das Abfangen von Objekten während ihrer Erzeugung. Wenn man bei der Geburt eines Objekts dabei ist, kann man allerlei interessante Sachen anstellen: man kann es einpacken, Methoden hinzufügen, Methoden entfernen, man kann es zu Containern hinzufügen, um Persistenz zu erreichen, man kann ihm einen Namen geben. Wir zeigen hier nur ein einfaches Beispiel: wir geben jedem Objekt bei der Geburt einen Zeitstempel mit.

Eine Möglichkeit, die Objekt-Erzeugung abzufangen, ist der Umbenennungstrick mit Class#new, der Methode, die Speicherplatz für ein neues Objekt anfordert. Die Technik ist nicht perfekt --- einige eingbaute Klassen wie literale Strings werden ohne new konstruiert --- aber bei selbst geschriebenen Objekten klappt das gut.

class Class
  alias_method :old_new,  :new
  def new(*args)
    result = old_new(*args)
    result.timestamp = Time.now
    return result
  end
end

Wenn wir ein Zeitstempel-Attribut an jedes Objekt im System anhängen wollen, hacken wir einfach die Klasse Object selber.

class Object
  def timestamp
    return @timestamp
  end
  def timestamp=(aTime)
    @timestamp = aTime
  end
end

Schließlich testen wir das. Wir erzeugen ein paar Objekte wenige Sekunden nacheinander und prüfen ihre Zeitstempel.

class Test
end
obj1 = Test.new
sleep 2
obj2 = Test.new
obj1.timestamp » Sun Mar 04 23:25:46 CST 2001
obj2.timestamp » Sun Mar 04 23:25:48 CST 2001

Dieses Umbenennen von Methoden ist ganz nett und es funktioniert auch wirklich. Aber es gibt andere raffiniertere Arten, um ein laufendes Programm zu beinflussen. Ruby unterstützt verschiedene Callback-Methodenm mit denen man auf kontrollierte Weise bestimmte Ereignisse abfangen kann.

Runtime-Callbacks

Man kann sich benachrichtigen lassen, wann immer eines der folgenden Ereignisse eintritt:

Ereignis Callback-Methode
Hinzufügen einer Instanz-Methode Module#method_added
Hinzufügen einer Singleton-Methode Kernel::singleton_method_added
Erzeugen einer Unter-Klasse Class#inherited
Einbinden eines Moduls Module#extend_object

Diese Techniken werden für jede Callback-Methode in den Bibliotheks-Beschreibungen erläutert. Zur Laufzeit werden diese Methoden vom System aufgerufen, wenn das angegebene Ereignis eintritt. Defaultmäßig machen diese Methoden gar nichts. Wenn man sich benachrichtigen lassen will, sobald eines dieser Ereignisse eintritt, definiert man einfach die Callback-Methode und drin ist man.

Das Verfolgen der Erzeugung von Methoden und der Benutzung von Modulen liefert einem ein genaues Abbild des dynamischen Zustands seines Programms. Das kann sehr wichtig sein. Man hat etwa Code geschrieben, der alle Methoden in einer Klasse wrappt, etwa um Transaktionen zu unterstützen oder um irgendeine Form der Weiterleitung zu implementieren. Damit ist aber nur die Hälfte getan: weil Ruby dynamisch ist, können Benutzer dieser Klasse jederzeit neue Methoden hinzufügen. Mit diesen Callbacks kann man Code schreiben, der diese neuen Methoden während der Erzeugung wrappt.

Ablaufverfolgung des Programms

Auch wenn wir Spaß daran haben, all diese Objekte und Klassen in unserem Programm zu überwachen, sollten wir nicht den bescheidenen Code vergessen, der tatsächlich die ganze Arbeit macht. Erfreulicherweise kann man in Ruby auch diese Anweisungen überwachen.

Zum einen kann man den Interpreter während der Code-Ausführung zusehen. set_trace_func führt ein Proc aus und liefert alle möglichen pikanten Debug-Informationen für jede ausgeführte Quell-Zeile, jede aufgerufene Methode, jedes erzeugte Objekt und so weiter. Auf Seite 426 gibt es die komplette Beschreibung, aber hier ist schon mal ein Appetithappen.

class Test
  def test
    a = 1
    b = 2
  end
end

set_trace_func proc { |event, file, line, id, binding, classname|   printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname } t = Test.new t.test
erzeugt:
    line prog.rb:11               false
  c-call prog.rb:11        new    Class
  c-call prog.rb:11 initialize   Object
c-return prog.rb:11 initialize   Object
c-return prog.rb:11        new    Class
    line prog.rb:12               false
    call prog.rb:2        test     Test
    line prog.rb:3        test     Test
    line prog.rb:4        test     Test
  return prog.rb:4        test     Test

Dann gibt es noch die Methode trace_var (Beschreibung auf Seite 431), mit der man einen Hook an eine globale Variable hängen kann; bei jeder Zuweisung an diese globale Variable wird dann das angegebene Proc-Objekt aufgerufen.

Wo kommen wir her?

Eine angemessene Frage, die wir uns oft genug stellen. Auch wenn unser Gedächtnis nicht so doll ist, kann man in Ruby genau herausfinden, ``wo man her kommt'', und zwar mit der Methode caller, die ein Array aus String-Objekten zurückliefert mit dem Inhalt des aktuellen Aufruf-Stacks.

def catA
  puts caller.join("\n")
end
def catB
  catA
end
def catC
  catB
end
catC
erzeugt:
prog.rb:5:in `catB'
prog.rb:8:in `catC'
prog.rb:10

Dann weiß man, wo man her gekommen ist, wie es weitergeht, muss man selbst entscheiden.

Marshaling und verteiltes Ruby

In Java gibt es die Möglichkeit, Objekte zu serialisieren. Damit kann man sie speichern und später wieder herstellen. Damit hat man die Möglichkeit, zum Beispiel eine Baumstruktur abzuspeichern, die einen Teil des Programmstatus darstellt --- oder ein Dokument, eine CAD-Zeichnug, ein Musikstück oder sonstwas.

Im Ruby heißt diese Serialisierung marshaling.[Das Wort Marshaling hat was mit Arrangieren zu tun, wie bei einem Verschiebebahnhof. Es lässt sich kaum übersetzen, also lass ich es. Der Übersetzer.] Man kann ein Objekt mit all seinen Komponenten abspeichern mit der Methode Marshal::dump. Üblicherweise deponiert man so einen kompletten Objektbaum, angefangen bei irgendeinem angegebenen Objekt. später kann man dieses Objekt mit Marshal::load wieder rekonstruieren.

Hier kommt ein kurzes Beispiel. Wir haben eine Klasse Chord, die eine Liste mit Musiknoten enthält. Wir möchten einen besonders schönen Akkord (Chord) abspeichern, damit unsere Enkel sich auch noch in Ruby (Version 23.5) daran erfreuen können. Fangen wir mit den Klassen für die Noten Note und den Akkord Chord an.

class Note
  attr :value
  def initialize(val)
    @value = val
  end
  def to_s
    @value.to_s
  end
end

class Chord   def initialize(arr)     @arr = arr   end   def play     @arr.join('-')   end end

Dann erzeugen wir unser Meisterwerk und speichern es mit Marshal::dump als serialisierte Version auf Diskette.

c = Chord.new( [ Note.new("G"),  Note.new("Bb"),
                 Note.new("Db"), Note.new("E") ] )

File.open("posterity", "w+") do |f|   Marshal.dump(c, f) end

Schließlich lesen unsere Enkel es wieder ein und sind gerührt von der Schönheit unserer Schöpfung.

File.open("posterity") do |f|
  chord = Marshal.load(f)
end
chord.play » "G-Bb-Db-E"

Empfohlene Startegie zur maßgeschneiderten Serialisierung

Nicht alle Objekte können deponiert werden: Bindings, Prozedur-Objekte, Instanzen der Klasse IO können nicht außerhalb der laufenden Ruby-Umgebung gesichert werden (man bekommt einen TypeError, wenn mans trotzdem versucht). Auch wenn das abzuspeichernde Objekt keine dieser problematischen Objekte enthält, möchte man vielleicht doch selber die Kontrolle über die Serialisation behalten.

Marshal liefert dazu die benötigten Hooks. Dazu implementiert man in den Objekten, die eine maßgeschneiderte Serialisierung benötigen, einfach zwei Methoden: eine Instanz-Methode mit Namen _dump, die das Objekt in einen String schreibt, sowie eine Methode mit Namen _load, die aus dem String wieder in ein neues Objekt macht.

Die folgende Beispiel-Klasse definiert ihre eigene Serialisierung. Aus unerfindlichen Gründen möchte Special nicht, dass die internen Daten aus ``@volatile'' abgespeichert werden.

class Special
  def initialize(valuable)
    @valuable = valuable
    @volatile = "Goodbye"
  end

  def _dump(depth)     @valuable.to_str   end

  def Special._load(str)     result = Special.new(str);   end

  def to_s     "[email protected]} and [email protected]}"   end end

a = Special.new("Hello, World") data = Marshal.dump(a) obj = Marshal.load(data) puts obj
erzeugt:
Hello, World and Goodbye

Genauer beschrieben ist das in der Referenz im Abschnitt Marshal ab Seite 432.

Verteiltes Ruby

Nachdem wir nun ein Objekt oder eine Menge von Objekten serialisieren und außerhalb des Programms speichern können, könne wir das auch nutzen für die Übertragung von Objekten von einem Prozess zum anderen. Verbindet man das noch mit Netzwerkzugriffen, voilą: schon hat man ein System für verteilte Objekte. Um jetzt nicht den ganzen Code selber abzutippen, schlagen wir den Download von Masatoshi Sekis Distributed Ruby library (drb) von RAA vor.

Mit drb kann ein Ruby-Prozess als Server, als Client oder als beides dienen. Ein drb-Server dient als Quelle der Objekte, der Client ist der Benutzer diese Objekte. Für den Client schienen diese Objekte lokal zu sein, aber in Wirklichkeit wird der Code anderswo ausgeführt.

Ein Server startet einen Dienst und verbindet ein Objekt mit einem angegebenen Port. Intern werden Threads erzeugt, um die eingehenden Anfragen für diesen Port abzuhandeln, also sollten man immer schön daran denken, vor Ende des Programms einen Join für diesen drb-Thread zu machen.

require 'drb'

class TestServer   def doit     "Hello, Distributed World"   end end

aServerObject = TestServer.new DRb.start_service('druby://localhost:9000', aServerObject) DRb.thread.join # Don't exit just yet!

Ein einfacher drb-Client erzeugt einfach ein lokales drb-Objekt und verbindet dieses mit dem Objekt auf dem entfernten Server; das lokale Objekt ist nur ein Proxy.

require 'drb'
DRb.start_service()
obj = DRbObject.new(nil, 'druby://localhost:9000')
# Now use obj
p obj.doit

Der Client verbindet sich mit dem Server und ruft die Methode doit auf, die einen String zurückliefert, den der Client dann ausgibt:

"Hello, Distributed World"

Das ursprüngliche Argument nil für DRbObject bedeutet, dass wir ein neues verteiltes Objekt anhängen wollen. Wir könnten auch schon existierende Objekte nehmen.

Je nun, hören wir den Leser sagen. Das hört sich an wie RMI von Java oder wie CORBA oder sowas. Ja, es ist ein Mechanismus für funktionale verteilte Objekte --- aber es ist mit nur 200 Zeilen Ruby-Code geschrieben. Kein C, nichts Extravagantes, nur schlichter alter Ruby-Code. Natürlich gibt es dafür dann keinen Name-Service, keinen Trader-Service oder was es sonst so gibt in CORBA, aber es ist einfach und recht schnell. Auf dem 233MHz-Test-System schaffte dieser Beispiel-Code etwa 50 Remote-Message-Aufrufe pro Sekunde.

Wenn man das dann noch mit Suns JavaSpaces vergleicht, der Grundlage ihrer JINI-Architektur, sieht man, dass drb mit einem kleinen Modul ausgeliefert wird, das genau das gleiche macht. JavaSpaces basiert auf einer Technologie mit Namen Linda. Um zu zeigen, dass auch japanische Autoren Sinn für Humor haben, heißt die Ruby-Version von Linda ``rinda.''

Kompilier-Zeit? Laufzeit? Jederzeit!

Man muss sich immer vor Augen halten, dass es in Ruby keinen großen Unterschied gibt zwischen ``Kompilier-Zeit'' und ``Laufzeit''. Das ist alles Jacke wie Hose. Man kann Code einfügen in einen laufenden Prozess. Man kann im fliegenden Wechsel Methoden umdefinieren, ihren Gültigkeitsbereich von public auf private ändern und so weiter. Man kann sogar Basis-Typen verändern wie Class oder Object.

Wenn man sich erst mal an diese Flexibilität gewöhnt hat, möchte man gar nicht mehr zurück zu statischen Sprachen wie C++, auch nicht zu halb-statischen wie Java.

Warum auch?


Extracted from the book "Programming Ruby - The Pragmatic Programmer's Guide"
Übersetzung: Jürgen Katins
Für das englische Original:
© 2000 Addison Wesley Longman, Inc. Released under the terms of the Open Publication License V1.0. That reference is available for download.
Diese Lizenz sowie das Original vom Herbst 2001 bilden die Grundlage der Übersetzung
Es wird darauf hingewiesen, dass sich die Lizenz des englischen Originals inzwischen geändert hat.
Für die deutsche Übersetzung:
© 2002 Jürgen Katins
Der Copyright-Eigner stellt folgende Lizenzen zur Verfügung:
Nicht-freie Lizenz:
This material may be distributed only subject to the terms and conditions set forth in the Open Publication License, v1.0 or later (the latest version is presently available at http://www.opencontent.org/openpub/). Distribution of substantively modified versions of this document is prohibited without the explicit permission of the copyright holder. Distribution of the work or derivative of the work in any standard (paper) book form is prohibited unless prior permission is obtained from the copyright holder.
Freie Lizenz:
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with no Front-Cover Texts, and with no Back-Cover Texts. A copy of the license is included in the section entitled "GNU Free Documentation License".