3. Objektrepräsentation
Wie sollen wir Io-Objekte in Python repräsentieren? Bei den Nachrichten haben wir bereits Python
ints und
strs benutzt, um Literale zu repräsentieren. In den Nachrichtenobjekten haben wir außerdem Python
lists benutzt. Dies macht die Interaktion von Io mit Python einfach und scheint daher eine gute Idee zu sein.
Für Io-Objekte mit Slots würde sich ein
dict anbieten, allerdings gibt es eine Vererbungsbeziehnung (die
protos-Listen) zwischen den Objekten, die ein Python
dict nicht kennt. Außerdem verhalten sich Methoden-Objekte (für die es merkwürdigerweise keinen eigenen Typ in der Io-Objektbibliothek zu geben scheint) anders als "normale" Slots-Objekte. Sie sind aktivierbar (aufrufbar).
Soll ich eine Unterklasse von
dict benutzen? Eine eigene Klasse? Hat diese dann ein
dict für die Slots oder benutzt man direkt die Attribute? Oder einfach in einem
dict zwei spezielle Slots für die Liste der Prototypen und die Eigenschaft, aktivierbar zu sein ablegen?
Ich habe mich für die folgende Lösung entschieden:
Code: Alles auswählen
class IoObject(object):
def __init__(self, protos, slots=None):
self.__protos__ = protos
if slots:
self.__dict__.update(slots)
class IoMethod(IoObject):
def __call__(self, io, context):
...
class IoBlock(IoMethod):
def __calll__(self, io, context):
...
Ich nutze das in Python-Klassen eingebaute __dict__ für die Slots. Die Liste der Prototypen ist einfach ein (weiterer) Slot mit einem Namen, der hoffentlich zu keinem Konflikt führt. Dies macht die Interaktion mit Python sehr einfach, denn ich kann z.B. "obj.call.sender" benutzen, um auf Slots zuzugreifen. Io kennt Slot-Namen, die in Python illegal wären. Solange ich aber `setattr` und `getattr` für diese Namen benutze, scheint das kein Problem zu sein.
Ich habe eine besondere Klasse für Methoden, deren Exemplare
callable sind. Ich übergebe hier den Interpreter, der Zugriff auf alle globalen Objekten und alle Funktionen bietet (siehe unten) und den Kontext, in dem die (Nachricht der) Methode ausgeführt werden soll. Der Kontext hat (wie in der Io-Dokumentation spezifiziert) einen Slot für "self", den Empfänger der Methode, die Parameter und das spezielle "call"-Objekt, über das der Sender der Methode, die ursprüngliche Nachricht und mehr in Erfahrung gebracht werden kann. Böcke sind noch einmal etwas spezieller als Methoden, sodass ich auch diese erkennen können muss.
Für die eingebauten System-Methoden (die in Python und nicht in Io geschrieben sind) möchte ich einfach Python-Funktionen benutzen - das motiviert zusätzlich die Entscheidung für
callable.
Als nächstes möchte ich die Grundoperationen auf den Objekten vorstellen. Diese implementiere ich als Methoden der Klasse "Io", deren Exemplare auch alle globalen Objekten halten und damit ermöglichen, mehr als einen Io-Interpreter zu benutzen (ich mache mir mit dieser Entscheidung das Leben vielleicht unnötig schwer, aber egal).
Mit `get_protos()` kann ich die Liste der Prototypen eines Objekts ermitteln. Da Zahlen, Strings und Listen Io-Objekte sind und alles von außen einheitlich aussehen muss, kann ich nicht einfach "obj.__protos__" aufrufen. Stattdessen entscheide ich, dass es reicht, wenn sich alle Zahlen, alle Strings und alle Listen jeweils eine Liste von Prototypen teilen. Diese verwalte ich direkt in `Io`:
Code: Alles auswählen
class Io(object):
def __init__(io):
"""
Initialize a new Io interpreter, setting up default objects and methods.
"""
io.object = IoObject([])
io.int_protos = []
io.str_protos = []
io.list_protos = []
...
def get_protos(io, target):
"""
Return the list of prototypes of the `target` object.
"""
if isinstance(target, int):
return io.int_protos
if isinstance(target, str):
return io.str_protos
if isinstance(target, list):
return io.list_protos
return target.__protos__
Setzen kann man die Liste natürlich auch - dazu dient `set_protos()`:
Code: Alles auswählen
class Io...
def set_protos(io, target):
"""
Set the list of prototypes for the `target` object.
"""
if isinstance(target, int):
io.int_protos = protos
elif isinstance(target, str):
io.str_protos = protos
elif isinstance(target, list):
io.list_protos = protos
else:
target.__protos__ = protos
Ein neues Objekt wird aus einem Prototyp mittels `clone()` erstellt:
Code: Alles auswählen
class Io...
def clone(io, target, slots=None):
"""
Create a new object that inherits from the `target` object.
"""
if isinstance(target, (int, str)):
return target
if isinstance(target, list):
return list(target)
return target.__class__([target], slots)
Wird ein
int oder
str übergeben, kann davon kein Klon erzeugt werden. Eine Liste zu klonen heißt sie zu kopieren. Da sich Listen genau wie Zahlen und Strings alle einen Prototypen teilen, ist hier nicht mehr zu tun. Ansonsten erzeuge ich ein neues Exemplar der selben Klasse wie der Prototyp und trage das alte Objekt als Prototyp ein.
Einen neuen Slot zu erzeugen ist die einfachste Funktion zur Manipulation von Slots. Dies geschieht direkt. Den Wert zu ermitteln oder zu ändern greift auf eine interne Funktion `find_slot()` zurück, die in der kompletten Hierarchie der Prototypen nach dem Slot sucht und separat erklärt werden soll.
Code: Alles auswählen
class Io...
def set_slot(io, target, name, value):
"""
Create a new slot named `name` with value `value` in the `target` object.
It is an error if `target` is not a slot object (having a `slots` attribute).
"""
try:
setattr(target, name, value)
return value
except AttributeError:
raise Exception("cannot set %r in %r" % (name, target))
def get_slot(io, target, name):
"""
Return the value of the slot named `name` which is either located in
the `target` object or one of its prototype objects. It is an error if
the slot does not exist.
"""
slot = io.find_slot(target, name)
if slot:
return slot.value
raise Exception("unknown %r in %r" % (name, target))
def update_slot(io, target, name, value):
"""
Update the value of the slot named `name` which is either located in
the `target` object or one of its prototype objects. It is an error if
the slot does not exist.
"""
slot = io.find_slot(target, name)
if slot:
slot.value = value
return value
raise Exception("cannot update %r in %r" % (name, target))
Für `find_slot` erzeuge ich kurzfristig die Illusion echter Slots, die wissen, aus welchem Objekt sie stammen. Diese Information wird später im Interpreter benötigt.
Code: Alles auswählen
class Io...
class Slot(object):
def __init__(self, target, name):
self.target = target
self.name = name
def _get(self):
return getattr(self.target, self.name)
def _set(self, value):
setattr(self.target, self.name, value)
value = property(_get, _set)
def find_slot(io, target, name):
"""
Search for the slot named `name` in the `target` and all of its
prototype objects depth first. If found, return a Slot object which
has a `value` attribute to set or get the slot value. If not found
return `None`. Each object is visited only once, so loops in the
prototype object graph are no problem.
"""
visited = set()
def find_in_object(target):
if target in visited:
return None
visited.add(target)
try:
if name in target.__dict__:
return Io.Slot(target, name)
except AttributeError:
pass
for proto in io.get_protos(target):
slot = find_in_object(proto)
if slot:
return slot
return None
return find_in_object(target)
Hier ist ein kurzes Testprogramm:
Code: Alles auswählen
io = Io()
o1 = io.clone(io.object)
o2 = io.clone(o1)
io.set_slot(o1, "answer", 42)
print io.get_slot(o2, "answer") #>> 42
io.update_slot(o2, "answer", 6 * 8)
io.set_slot(o2, "answer", 21)
print io.get_slot(o2, "answer") #>> 21
print io.get_slot(io.get_protos(o2)[0], "answer") #>> 48
o3 = io.clone(o1)
io.set_protos(o3, [o3, o1])
print io.get_slot(o3, "answer") #>> 48
Für die Interoperabilität mit Python ist es praktisch, dass die Slot-Operationen auch auf "normalen" Python-Objekten funktionieren:
Code: Alles auswählen
io = Io()
m = Message("foo", [Message(1)])
print io.get_slot(m, "symbol")
print io.get_slot(m, "arguments")
io.set_slot(m, "symbol", "bar")
print m
Wir haben jetzt alle Bausteine zusammen, um Nachrichten auszuwerten und damit Io-Programme ablaufen zu lassen. Wie der Interpreter funktioniert, zeige ich im nächsten Teil.
Stefan
PS: Frohes Neues Jahr!