MultimethodeAls Multimethoden bezeichnet man Methoden einer objektorientierten Programmiersprache, deren Auswahl nicht nur anhand des Typs eines Objekts getroffen wird, sondern anhand der dynamischen Typen mehrerer Objekte. Diese Art der Methodenauswahl wird auch als multiple dispatch (‚mehrfache Verteilung‘) bezeichnet. Ein ähnliches Konzept wie die mehrfache Verteilung ist das in vielen prozeduralen Programmiersprachen möglichen Überladen, bei der Methoden polymorph bezüglich der statischen Typen ihrer Parameter sind. Der Unterschied ist, dass das Überladen bereits zur Übersetzungszeit stattfindet, die mehrfache Verteilung jedoch erst zur Laufzeit. Während bei klassischen objektorientierten Sprachen wie Java ausschließlich der dynamische Typ des impliziten ersten Parameters Die erste und bekannteste objektorientierte Umgebung, die diese Fähigkeit hat, ist das Common Lisp Object System (CLOS),[1] aber auch Sprachen wie Dylan, Slate, Cecil, Guile, Seed7, Julia oder der Java-Abkömmling Nice bieten Derartiges. In C++ ist es möglich, Multimethoden als Funktoren und Templates auf verschiedene Weisen zu implementieren. In der JVM Welt ist z. B. Groovy eine Java-Syntax-kompatible Sprache mit größerer Verbreitung, die (sowohl bei dynamischer als auch statischer Kompilierung) standardmäßig Multimethoden unterstützt.[2] Multimethoden in Common LispDie objektorientierte Programmierung mit Multimethoden unterscheidet sich in einigen Punkten grundlegend von eindimensionaler objektorientierter Programmierung. In Common Lisp basieren Multimethoden auf drei elementaren Konzepten, die anders zu verstehen sind als z. B. in Java:
Praktisches Beispiel in Common LispDas folgende Beispiel verwendet Multimethoden, um die Bildung der Schnittmenge mit drei intern unterschiedlich dargestellten Mengen interoperabel zu implementieren. MengendarstellungenEs sollen die folgenden drei Implementierungen für Mengen unterstützt werden:
ProgrammcodeKlassendefinitionenFür diese drei Darstellungen werden die Klassen Die Beziehung der Klassen ist demnach wie folgt:
Die Umsetzung der Klassenhierarchie in Common Lisp erfolgt in fünf einzelnen Definitionen. Klasse any-set (abstrakt)Common Lisp: Klassen werden in Common Lisp mit (defclass any-set ()
())
Klasse set-by-intensionDiese enthält nur das einstellige Prädikat (defclass set-by-intension (any-set)
((predicate :accessor predicate :initarg :predicate)))
Klasse enumerateable-set (abstrakt)Ihr Zweck ist es, eine gemeinsame Elternklasse für die Klassen (defclass enumerateable-set (any-set)
())
Klasse set-by-extensionCommon Lisp: Slot-Definitionen Die Definition von Slots (in Java „Membervariablen“) enthält als erstes den Namen (z. B. Sie enthält nur den Slot (defclass set-by-extension (enumerateable-set)
((ext :accessor ext :initarg :ext)))
Klasse integer-range-setDiese Form speichert von dem geschlossenen Ganzzahlenbereich den kleinsten Wert (defclass integer-range-set (enumerateable-set)
((from :accessor from :initarg :from)
(to :accessor to :initarg :to)))
Die leere MengeDie leere Menge (defvar empty-set (make-instance 'set-by-extension :ext nil))
Generische FunktionenNun erfolgt die Definition der generischen Funktion (defgeneric enumeration (enumerateable-set))
(defgeneric intersection2 (any-set any-set))
Methoden der generischen Funktion enumerationDiese beiden Methoden sind noch keine Multimethoden. In Java würden sie einfach mit (defmethod enumeration ((s set-by-extension))
(ext s))
(defmethod enumeration ((s integer-range-set))
(loop for i from (from s) to (to s) collect i))
Konkrete Methoden der generischen Funktion intersection2Common-Lisp: Funktionen und Makros
Übernimmt eine Funktion und eine Liste. Die Funktion wird der Reihe nach auf jedes Element der Liste angewandt. Zurückgegeben wird eine neue Liste aller Elemente, für die die Funktion „wahr“ geliefert hat.
Bildet eine Liste mit der Schnittmenge der Elemente aller übergebenen Listen.
Übernimmt eine Parameterliste und einen Ausdruck, in dem die in der Liste genannten Parameter vorkommen dürfen. Es liefert eine anonyme Funktion zurück, die mit einer passenden Argumentenliste gerufen werden kann und mit dieser den übergebenen Ausdruck berechnet. Die fünf Methoden der generischen Funktion (defmethod intersection2 ((a set-by-intension) (b enumerateable-set))
(make-instance 'set-by-extension
:ext (remove-if-not (predicate a)
(enumeration b))))
(defmethod intersection2 ((a set-by-intension) (b set-by-intension))
;; In diesem Fall wird aus den beiden Prädikaten von a und
;; b ein neues Prädikat durch UND-Verknüpfung zusammengesetzt und
;; die Ergebnis-Instanz der Klasse SET-BY-INTENSION damit
;; initialisiert:
(make-instance 'set-by-intension
:predicate (lambda (x)
(and (funcall (predicate a) x)
(funcall (predicate b) x)))))
(defmethod intersection2 ((a enumerateable-set) (b set-by-intension))
(intersection2 b a)) ; Rückführung auf den kommutativen Fall
(defmethod intersection2 ((a enumerateable-set) (b enumerateable-set))
(make-instance 'set-by-extension
:ext (intersection (enumeration a) (enumeration b))))
Methode der generischen Funktion intersection2 für Aufrufe mit zwei Parametern der Klasse integer-range-setObwohl dieser Fall schon durch die vierte Methode oben abgedeckt ist, bietet es sich hier an, eine spezifischere Methode vorzusehen um wieder ein Ergebnis der Klasse (defmethod intersection2 ((a integer-range-set) (b integer-range-set))
;; Es wird das Maximum N der unteren und das Minimum M der
;; Obergrenzen gebildet. Falls nun N>M gilt, ist die Schnittmenge
;; leer, sonst eine Instanz der Klasse INTEGER-RANGE-SET mit den
;; Grenzen N und M
(let ((n (max (from a) (from b)))
(m (min (to a) (to b))))
(if (> n m)
empty-set
(make-instance 'integer-range-set :from n :to m))))
Zusätzliche Methoden für die generische Funktion intersection2 für den Umgang mit der leeren MengeCommon-Lisp: Der Common Lisp kann Methoden einer generischen Funktion nicht nur auf Klassen spezialisieren, sondern auch auf eine einzelne Instanz. Dazu wird als Typ des Parameters nicht eine Klasse angegeben, sondern der Ausdruck Mit den folgenden beiden Methoden wird durch Verwendung eines (defmethod intersection2 ((a (eql empty-set)) (b any-set))
empty-set)
(defmethod intersection2 ((a any-set) (b (eql empty-set)))
empty-set)
print-object-MethodenMit obigen Definitionen ist die Funktionalität vollständig umgesetzt. Um den Dialog mit Common Lisp zu vereinfachen, erfolgt nun noch die Definition von geeigneten Methoden für die durch das System vordefinierte Generische Funktion (defmethod print-object ((s set-by-extension) stream)
(prin1 (ext s) stream))
(defmethod print-object ((s set-by-intension) stream)
(format stream "~A" (function-lambda-expression (predicate s))))
(defmethod print-object ((s integer-range-set) stream)
(format stream "(~A .. ~A)" (from s) (to s)))
AnwendungsbeispielCommon-Lisp: Das Makro
Davor ist noch die Bedingung gesetzt, da die Zahl 1 im mathematischen Sinn keine Primzahl ist. Die Gesamtfunktionalität ist aus folgendem Anwendungsbeispiel ersichtlich. Die Menge aller Primzahlen kann durch das Prädikat (defun prime (n)
(when (> n 1)
(loop for i from 2 to (isqrt n)
never (eql 0 (mod n i)))))
Mit dieser Definition als Prädikat ist es jetzt möglich, die Menge aller Primzahlen (set 'set-of-primes (make-instance 'set-by-intension
:predicate #'prime))
Als zweite Menge fungiert die Menge (set 'first-100 (make-instance 'integer-range-set :from 1 :to 100))
Die Schnittmenge beider Mengen, also die Primzahlen von 1 bis 100, kann dann jetzt durch den Aufruf der Generischen Funktion (intersection2 set-of-primes first-100)
Es erfolgt die korrekte Ausgabe (2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97)
Erläuterung
Literatur
WeblinksEinzelnachweise
|
Portal di Ensiklopedia Dunia