Typengebundene Methoden



  • Moin,

    in der Diskussion um die Programmiersprache der Träume habe ich mal wieder über die Unterstützung von OOP nachgedacht.

    Normalerweise funktioniert es so, dass Methoden nicht sichtbar sind und in der Klasse zu der sie gehören referenziert werden. Eine Klasse ist also ein Verbundtyp mit seinen Attributen und Referenzen zu Funktionen, den Methoden.

    In Ada ist es jetzt so, dass es keine "Klassen" und "Methoden" im gewöhnlichen Sinne gibt. Stattdessen wird das Typensystem verwendet um die zugehörigen Prozeduren zu identifizieren und ein "Tag" um den Typen zur Laufzeit zwecks dynamischer Bindung zu markieren.

    Was ich mich natürlich frage ist, warum dieses System, das eine Auflösung zur Übersetzungszeit bietet, die Programmiersprache nicht durch einen vermeintlichen "Fremdkörper" erweitert sondern sich nahtlos in den Rest der Sprache und des Typensystems einbindet und in meinen Augen auch wesentlich simpler ist, nirgendwo sonst verwendet wird. Oder wird in anderen Sprachen unter der Haube so gearbeitet. Die Notation "Vehicle.Accelerate" deutet ja eher nicht darauf hin.

    type Vehicle is tagged record
      Speed : Float;
    end record;
    
    Procedure Accelerate(Object : in Vehicle) is
      ...
    begin
      ...
    end;
    
    [...]
    
    declare
      My_Car : Vehicle;
    begin
      Accelerate(My_Car);
    end;
    

    Hier sieht man, dass die Prozedur "Accelerate" bereits an den Typen "Vehicle" gebunden ist. Wieso also nochmal durch Referenz binden? Das "tagged" Schlüsselwort könnte man hier natürlich auch weglassen, da dessen Funktionalität nicht genutzt wird, aber ich habe es mal dran gelassen, da es dem record die volle Funktionalität verleiht, die man in anderen Programmiersprachen von einer "Klasse" erwartet.



  • Methodentyp schrieb:

    Was ich mich natürlich frage ist, warum dieses System, das eine Auflösung zur Übersetzungszeit bietet, die Programmiersprache nicht durch einen vermeintlichen "Fremdkörper" erweitert sondern sich nahtlos in den Rest der Sprache und des Typensystems einbindet und in meinen Augen auch wesentlich simpler ist, nirgendwo sonst verwendet wird.

    In Oberon-2 wird das ganz ähnlich gemacht.

    Richtig interessant wird dieses Konzept in Zusammenhang mit Multimethoden, da man diese nicht mehr an einen Typen binden kann, denn das Dispatching hängt von mehreren Parametern der Methode ab, da böte sich die Syntax aus Ada bzw. Oberon-2 eher an.

    Beispiel:
    Matrix A, B
    Skalar c, d

    AB
    c
    B
    Bd
    c
    d

    Mit der klassischen OOP-Syntax sind solche Fällen nicht mehr wirklich darstellbar, da der Skalartyp natürlich nichts von einer Matrix wissen darf. D.h. c*A im Sinne von c.multiply(A) ergibt keinen Sinn. Mit verstehenden Multimethoden wären solche Probleme lösbar.



  • Kann mir jmd. erklären was in dem gezeigten Beispiel der Unterschied zu ganz stinknormalen überladenen freien Funktionen in C++ ist?



  • hustbaer schrieb:

    Kann mir jmd. erklären was in dem gezeigten Beispiel der Unterschied zu ganz stinknormalen überladenen freien Funktionen in C++ ist?

    In dem gezeigten Beispiel gibt es keinen Unterschied. Das ist es ja gerade: In C++ gibt es das Schlüsselwort "class" um Klassen zu erstellen und man bindet Methoden durch Zeiger in der Klasse an die jeweilige Klasse, während man sich in Ada bereits vorhandener Funktionalität des Typensystems bedient. Für komplette Unterstützung von OOP gibt es keine Syntaxänderungen, lediglich das neue Schlüsselwort "tagged", das es erlaubt den Typen während der Laufzeit zu identifizieren, und das Attribut "'Class", das auf Datentypen angewendet für "Eine Menge aus diesem Typ und allen seinen Nachfahren" steht, was dynamische Bindung einfach macht.



  • Ich verstehe nicht worauf du hinaus willst.
    Methoden einer Klasse zuzuordnen macht Sinn, da man dadurch kontrolliert wer was darf - die private/protected Geschichte eben.
    Das hat erstmal mit Binden und Zeigern nix zu tun.

    Der Rest ergibt sich aus der Tatsache dass C++ kein "Multiple Dispatch" unterstützt. Oder besser gesagt: es gibt keinen eingebauten "Language-Support" für "Multiple Dispatch".

    Geht "multiple dispatch" in ADA? Wenn ja, cool. Geht in C++ halt nicht. Ist es das worauf du hinaus willst? Dann ist die Antwort: man macht es nicht, weil es aufwendig ist, und nicht einfach nur "das bestehende Typensystem verwenden". Damit das performant funktioniert muss man schon einigen Aufwand treiben. Und es ist glaube ich auch nicht ganz trivial dafür brauchbare Regeln zu finden - zumindest nicht wenn man Vererbung erlaubt.

    Beispiel:
    Klasse A, davon abgeleitet Klasse B.
    Klasse X, davon abgeleitet Klasse Y.
    Funktionen F(A, Y) und F(B, X).
    Aufruf: F(b, y).
    Wer bekommt das Keks?

    EDIT: ich bin doof 🙂 ist doch dasselbe wie ganz normale overload-resolution. nur halt dynamisch. was aber wieder bedeutet, dass es ordentlich langsam werden kann. und ich hab grad nachgesehen: ADA unterstützt kein multiple dispatch. was genau soll jetzt also der unterschied zu C++ sein? /EDIT
    EDIT2: muss doch nicht langsam sein. wieder was dazugelernt... http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2216.pdf /EDIT2

    Und ich verstehe auch nicht was du mit "man bindet Methoden durch Zeiger in der Klasse an die jeweilige Klasse" meinst. Anspielung auf den VTable? Falls ja... dir ist hoffentlich klar dass das ein Implementierungs-Detail ist, und nichts mit der Sprache C++ ansich zu tun hat.



  • hustbaer schrieb:

    Geht "multiple dispatch" in ADA?

    Leider nein.
    C++ kennt nur Zugriffsbeschränkungen und keine Sichtbarkeitsregeln, was unter anderem zum Pimpl-Idiom geführt hat. In Ada, Oberon-2, Fortran u.v.m. wird die Sichtbarkeit auf Basis des Modulkonzepts gelöst.



  • Ja, Sichtbarkeit auf "class" Ebene ist ein Punkt den ich meine. Es geht mir dabei aber eher um die Einfachheit.

    Stell dir einfach mal vor, du hast eine Sprache, die ist ähnlich wie C++, nur dass sie OOP nicht unterstützt. Das Schlüsselwort Class gibt es nicht. Du möchtest dieser Sprache nun Unterstützung für OOP bieten. Smalltalk kennst du nicht, genauso wie jede andere Programmiersprache mit Unterstützung für OOP.

    - Du möchtest Daten zu einem Paket verschnüren und diesem Paket ein paar Funktionen zuweisen, die zusammen einen Objekttyp bilden. Du möchtest von diesem Objekttyp Instanzen, also Objekte erstellen.

    In dynamisch typisierten Programmiersprachen stehst du jetzt vor einem Problem, da genau dies die Stärke von umfangreichen Typensystem ist. Man könnte, oder müsste sich hier bereits dazu entschließen ein neues Konstrukt zu erlauben: Die Klasse. Um effizient auf die Methoden einer Klasse zugreifen zu können, werden diese in den Objekten referenziert. Passende Syntax dazu:

    My_Car.Speed = 4.0;
    My_Car.Accelerate; 
    // das Datenfeld Accelerate ist eine Referenz auf die Methode Accelerate
    

    Was ist aber in C++? Man hat typedef, man hat struct und man kann Funktionen überladen. Du erstellst Einen structtyp für die Daten und ein paar Funktionen, die diesen structtyp als Parameter entgegennehmen, wodurch sie daran gebunden werden. Passende Syntax:

    My_Car.Speed = 4.0;
    Accelerate(My_Car);
    // My_Car hat kein Datenfeld Accelerate, es gibt die lose Funktion
    // Accelerate, die My_Car entgegen nehmen kann. Man erhält Bindung
    // über das Typensystem.
    

    - Du möchtest weitere Objekttypen erstellen, die ihre Eigenschaften von bereits vorhandenen Objekttypen erben. Diese erbenden Typen sollen neue Datenfelder und neue Funktionen erlauben, sowie das Überladen von Funktionen.

    Wieder muss man in dynamisch typisierten Programmiersprachen auf dieses neue "Klassen"-Konstrukt zugreifen, in C++ aber nicht. Typenvererbung ist möglich, neue Datenfelder kann man auch ohne neue Konstrukte ermöglichen. Man nutzt vorhandene Syntax aus typedef, Typenvererbung und struct. Neue Funktionen für diese erbenden Typen erstellt man, indem man Funktionen schreibt, die den erbenden Typ als Parameter erwarten. Das Überladen ist ebenfalls bereits durch das Typensystem möglich

    - Du möchtest in der Sprache dynamisch zu den Funktionen eines Objekttyps springen können, um Konstrukte zu erlauben, bei denen zur Übersetzungszeit nicht klar ist, welchen Typ einer Typenhierarchie du gerade vor dir hast - Polymorphie.

    Benötigt man hier wirklich neue Syntax? Vielleicht ja, das ist Geschmackssache. Vielleicht möchte man Typen haben, wo das erlaubt ist, andere wo das nicht erlaubt ist. Man könnte das dispatchen erlauben, indem man bei Variablendeklaration anweist, dass diese Variable nicht nur den Typ aufnehmen kann, den man angibt, sondern auch seine Nachkommen:

    Class Vehicle My_Car;
    // Das Objekt My_Car wird deklariert, es ist vom Typ "Class Vehicle",
    // also von "Vehicle" und seinen Nachkommen
    

    - Natürlich soll die Sichtbarkeit der Elemente deiner Klasse gesteuert werden können

    Ist das wirklich Aufgabe deines Typensystems? Selbst in C hast du bereits einen "public" Bereich (den header) und einen "private" Bereich (den body). Man könnte doch das Modulsystem weiter für diese Aufgabe nutzen und im header einen Protected-Block erlauben, womit die Sichtbarkeit geregelt wäre. Wieder ohne, dass man ein vollkommen neues Konstrukt benötigt und auch ohne Redundanz indem du Sichtbarkeit alleine auf Modulebene und nicht auch auf Klassenebene regelst.

    Alles zusammen wundere ich mich, warum statisch typisierte Programmiersprachen sich so stark an die Syntax von Smalltalk orientieren, wo doch die Probleme bei dem Design der OOP-Unterstützung vollkommen andere sind. Bei dynamischen Systemen musst du Kontrolle erschaffen, die in umfangreichen Typensystemen bereits vorhanden ist, bei statischen Systemen musst du dynamisches Verhalten dort bieten, wo man per Design eigentlich keines hat.


Log in to reply