Friday, October 29, 2010

Übersicht: Eigenschaften von Typsystemen

Während es beim letzten Mal eher um die Unterscheidung zwischen rein klassenbasierten Typsystemen und dem Zusammenhang von Typparametern und Klassen ging, stehen diesmal ganz generisch die Eigenschaften von Typsystemen im Mittelpunkt.

Typsysteme... Was genau sind eigentlich Typsysteme? Möglichkeiten zur Einschränkung / Erklärung von Typsystemen gibt es unzählige. Der folgende Artikel soll kurz einige der wichtigsten Eigenschaften aufzeigen. Da Typsysteme die wichtigste Eigenschaft einer Programmiersprache sind bzw. vielleicht sogar die einzige Aufgabe, lohnt es sicherlich, sie etwas genauer zu betrachten.

Die meisten diese Bereiche werden übrigens in follow-up Artikeln noch etwas genauer beleuchtet werden. Das hier ist nur eine ganz schnöde Auflistung der Features.

A) Stark vs Schwach
  • Erlaubt das Typsystem implizite Konvertierungen (Type Coercions) von einem Typ in den anderen?
  • Beispieel: 3 + "hallo"; 3 * 3.4. Ist dies möglich?  Welches Ergebnis wird erzeugt?
  • Grundsätzlich ist schwache Typisierung nicht als Nachteil zu sehen. Bei Sprachen die Typen aber zu relaxt handhaben kann es schwieriger werden, richtigen Code zu erzeugen, weil sich schneller Fehler einschleichen können.
  • Eine Besonderheit stellt die Sprache Scala dar: In der Sprache selbst sind keine impliziten Konvertierungen definiert, jedoch ist es möglich, dass der Programmierer diese selbst definiert und explizit in bestimmte Module importiert.

    B) Typenableitung
    • Können neue Typen durch Kombination / "Vererbung" von vorhandenen Typen abgeleitet werden?
    • Populärstes Beispiel: Objektorientierung
    • Ermöglicht fast immer die Single-Dispatch-Polymorphie (s.o.)
    • Kovarianz und Kontravarianz: Spezielle Regeln die für die Parameter von Typen (Generics) und die Parameter von abgeleiteten Methoden gelten.

    C) Typzugehörigkeit: Nominativ < Strukturell < Dynamisch
    • Nominativ
      • Erlauben nur Werte die vom selben Typ (Namen) sind
      • Ohne Typenableitung: Starres und unflexibles Typsystem wie das von C.
      • Mit Typenableitung: Objektorientierung (Java, C++, C#, ...). Hier stellt die erzwungene Beziehung zwischen Objekten immer noch eine sehr starke Einschränkung der möglichen Werte dar.
    • Strukturell
      • Typen definieren mit ein oder mehrere Methoden eine Schnittstelle (ähnlich den Interfaces in Java oder C#)
      • Ein Wert entspricht einem Typ, wenn er alle Methoden implementiert. Dies ist unabhängig davon, ob er ein Subtyp von diesem Typ ist.
      • Deutlich höhere Flexibilität - dem Duck-Typing sehr ähnlich
      • Typen können zu jedem beliebigen Zeitpunkt ad-hoc definiert werden, ohne dass sie in eine Vererbungshierarchie eingebracht werden müssen
      • Nominative Typsysteme können eine Unterklasse der strukturellen Typsysteme bilden. So ist Scala z.B. strukturell typisiert, praktisch wird aber fast immer das nominative objektorientierte System verwendet.
    • Dynamisch (Duck-Typing)
      • Ähnlich der strukturellen Typisierung, es werden aber gar keine Anforderungen mehr an Typen gemacht. Ein Wert muss einfach nur die Methoden unterstützen, die im Code verwendet werden. Tut er dies, läuft der Code. Tut er dies nicht.. bang!
      • Keine Typprüfung mehr durch den Compiler
      • Grundsätzlich etwas schlechtere Performance, die aber kein "showstopper" sein sollte
      • Typische Eigenschaft aller dynamisch typisierten Sprachen, lokal beschränkt aber auch in statischen Sprachen wie C# möglich.
      • In dynamischen Sprachen sind Variablen nicht typisiert, so dass mit einer Zuweisung der Typ einer Variablen wechseln kann. Es ist also möglich in einer Variable zuerst einen String und später einen Int zu halten.
      • Schwach typisierte dynamische Sprachen erzeugen meist schwer nachvollziehbaren Code (PHP). Dies wird meist als Vorwurf gegen alle dynamischen Sprachen benutzt - es gibt aber auch viele stark typisierte Sprachen wie z.B. Phyton auf die dies nicht zutrifft.

    D) Polymorphie
    Polymorphismus ("Viele Formen") bedeutet in der Programmierung, dass ein Bezeichner abhängig vom Kontext unterschiedliche Bedeutungen haben kann.
    • Ad-Hoc-Polymorphie (Compilezeit)
      • Statische Methoden- und Operatorenüberladung
        • Effektiv dasselbe. Operatoren sind eigentlich nur Methoden mit einer anderen Schreibweise.
        • Bezeichnet die Fähigkeit, einem Bezeichner statisch mehrere Methoden mit verschiedenen Parameterlisten zuzuordnen
      • Typparametrisierung (Generics)
        • Ermöglicht ad-hoc Typannotationen an Typen, um spontan aus Templates neue Typen zu bilden. Aus einem Set[T], wobei T die Einfügemarke ist, kann so ein Set[String] oder auch ein Set[Int] werden.
        • Nur für statisch typisierte Sprachen interessant, um Optimierungen vorzunehmen und auf unsichere Casts zu verzichten
        • Zusammen mit der Subtypisierung ergeben sich hier einige sehr komplexe Kombinationsmöglichkeiten durch die Kovarianz und Kontravarianz
    • Dynamische Polymorphie / Dispatch (Laufzeit)
      • Single-Dispatch
        • Subtypenbildung (Typenableitung) ist möglich
        • In der Objektorientierung: Vererbung
        • In der strukturellen Typisierung als Typklassen bezeichnet
      • Multi-Dispatch
        • Dynamische Methodenüberladung zur Laufzeit
        • Anders als beim Single-Dispatch können mehrere Parameter einer Methode überladen sein
        • Auch als Multi-Methods bezeichnet

      E) Typinferenz
      • Hauptsächlich bei statisch typisierten Sprachen wichtig: In welchem Umfang muss der Programmierer Typannotationen bei Variablen und Methodenparametern vornehmen, und inwieweit können diese "wegfallen"?
      • Sprachen, die Typinferenz ermöglichen, machen die Typannotationen stets optional, sie können also verwendet werden, müssen aber nicht. Dadurch wird der Code meist deutlich kürzer und lesbarer.
      • Falls die Sprache Typparameter ermöglicht, können auch meist diese durch die Typinferenz ermittelt werden
      • Arten
        • Nur Rückgabewerte (C, C++, Java, ...)
          • Bei Rückgabewerten ermittelt der Compiler den Typ zur Prüfung
          • Ermöglicht das Method-Chaining: new String("A").concat("B).concat("C")
          • Typannotationen sind stets erforderlich
        • Lokal (Scala, C# 3.0)
          • Type-Inferrence funktioniert nur innerhalb eines begrenzten Scopes, nicht aber auf dem Method-Level. Dadurch müssen alle lokalen Variablen und nicht typisiert werden
          • Bei Methoden müssen die Eingabeparameter, meist aber nicht die Rückgabewerte typisiert werden. Letztere können aus den Rückgabewerten der Methode abgeleitet werden
        • Global (Haskell)
          • Auch Methoden müssen nicht oder nur selten annotiert werden
          • Eine volle Typinferenz erfordert zwingend ein strukturelles Typsystem
          • Gerade bei vollständiger Typinferenz ist es von Vorteil, das Typen immer noch explizit annotiert werden können. Schließlich haben Typannotation an Methoden auch einen Vorteil: Sie dokumentieren, welche Werte erlaubt sind.
      • In den meisten dynamischen Sprachen können Typen gar nicht annotiert werden. Sie haben also automatisch eine Art "vollständiger" Typinferenz ohne Typsicherheit.

      No comments:

      Post a Comment