Typisierung (Informatik)

aus Wikipedia, der freien Enzyklopädie

Eine Typisierung dient in der Informatik dazu, dass die Objekte (hier Objekte im mathematisch-abstrakten Sinne verstanden) der Programmiersprachen, wie z. B. Variablen, Funktionen oder Objekte (im Sinne der objektorientierten Programmierung) korrekt verwendet werden. Ein Ziel der Typisierung ist die Vermeidung von Laufzeitfehlern.

Typsystem

Der Begriff Typsystem bezeichnet in der Informatik eine Komponente, die in Programmiersprachen zum Einsatz kommen kann, um den Wertebereich von Variablen einzuschränken. Programmiersprachen, die über ein Typsystem verfügen, nennt man typisiert. Je nach Ausprägung des Typsystems spricht man gelegentlich auch von stark typisierten oder schwach typisierten Sprachen. Durch die Typisierung soll sichergestellt werden, dass auf den Inhalten von Variablen keine Operationen ausgeführt werden, die syntaktisch oder semantisch fehlerhaft sind. Ein Typsystem wird durch folgende Bestandteile gebildet:

  • Die Typen selbst, die entweder mittels Typdefinitionen erzeugt oder in der jeweiligen Sprache fest integriert (als primitive Datentypen) sind.
  • Die Möglichkeit, Programmelemente (wie Variablen, Methodenparameter usw.) mittels Typannotation mit einem bestimmten Typ zu deklarieren.
  • Regeln, nach denen die Werte von Ausdrücken einem bestimmten Typ zugeordnet werden.
  • Regeln zur Prüfung der Zuweisungskompatibilität von Typen.
  • Optional weitere Sprachbestandteile wie typbezogene Operatoren (z. B. „instanceof“ in diversen Sprachen) oder eine Reflection API zur Ermittlung und Prüfung von Typinformation zur Laufzeit.

Klassifizierung von Typsystemen

Typsysteme lassen sich entlang dreier Dimensionen klassifizieren:

  • starke Typisierung und schwache Typisierung (englisch weak typing)
    Wie streng unterscheidet die Sprache die Typen? Welche Datentypen können ineinander umgewandelt werden? Erlaubt sie implizite Typumwandlungen? Erlaubt sie unsichere Typumwandlungen, bei denen z. B. Werte verloren gehen können?
  • dynamische Typisierung (dynamic typing) und statische Typisierung (static typing)
    Typprüfungen können zur Übersetzungszeit oder zur Laufzeit vorgenommen werden. Im ersten Fall spricht man von statischer Typprüfung, im zweiten von dynamischer Typprüfung. Statische Typprüfungen erlauben das Auffinden von Zuweisungsfehlern vor der Laufzeit, während sie bei dynamischer Typisierung erst zur Laufzeit auffallen. Dafür erlaubt Letztere ein mächtigeres Verhalten wie die korrekte Verarbeitung eigentlich inkompatibler Typen und Erweiterungen zur Laufzeit.
  • explizite Typisierung (explicit typing) und implizite Typisierung (implicit typing)
    Werden die Datentypen explizit genannt oder per Typableitung (type inference) ermittelt?
  • optionale Typisierung (optional typing)
    Typ-Annotationen werden zur Unterstützung der Programmierer genutzt, haben aber keine Auswirkungen auf die tatsächliche Umsetzung durch den Compiler zur Laufzeit.

Zu den Aufgaben eines Typsystems gehören:

  • Erkennung von Typverletzungen bei der Übersetzung und Ausführung. Die Typinformation kann dabei als eine redundante Information aufgefasst werden, die verhindert, dass Variablen Werte zugewiesen werden, welche die betreffende Variable niemals annehmen sollte (sogenannte Typinvarianten). Sie verhindert so die Ausführung von Operationen auf den Inhalten dieser Variablen, die entweder unmöglich oder aus programmlogischen Gründen nicht sinnvoll ist.
  • Typumwandlung (type conversion), also Umwandlung bzw. Beförderung und Degradierung von Typen (type promotion, type demotion). In vielen objektorientierten Sprachen stehen hierfür die drei Möglichkeiten up cast, down cast und cross cast zur Verfügung.

Beispiele

Die folgenden Beispiele sollen die Klassifizierung verdeutlichen; die Einordnung von Sprachen in diese Kategorien ist aber bis zu einem gewissen Grad subjektiv (C++ z. B. ist stärker typisiert als C, aber schwächer als OCAML), und viele Typsysteme haben je nach Verwendung mehrere Aspekte (Polymorphe Werte, Typumwandlung etc.).


keine: x86 Assembler

 mov ah, 0x0e                      ; Weise dem oberen Byte des Registers ax den Wert 14 zu.
 mov al, '!'                       ; Weise dem unteren Byte den Wert des Zeichens '!' zu.
 int 0x10                          ; Erzeuge Interrupt 0x10 um das Zeichen '!' auszugeben.


Statisch, explizit, schwach: C

 int x;                            // Weise x explizit den Typ int (Ganzzahl) zu.
 char *str = x;                    // Weist str als Anfangsadresse den Wert von x zu.
                                   // Da x undefiniert ist, kann die Ausgabe von str zum Programmabsturz führen.
 x = &str;                         // Weist x die Speicheradresse von str zu.
 *(char **)x = "text";             // Schreibt die Anfangsadresse des Strings "text" in die Adresse von str.
 printf("%s", str);                // Ausgabe von "text"


Statisch und dynamisch, explizit und implizit, stark: C#

 object addmax(object a, int b, short c)
 {
     int max = Math.Max(b, c);     // Aufruf von Max(int b, int c) der Klasse Math
     int sum = (int)a + max;       // InvalidCastException falls a nicht konvertierbar,
                                   // NullReferenceException falls a undefiniert
     return sum;                   // implizite Konvertierung (Boxing) von int zu object
 }


Statisch, explizit und implizit, stark: Java

 Integer addmax(Object a, int b, short c) {
    int max = Math.max(b, c);      // Aufruf von max(int b, int c) der Klasse Math
    int sum = (int)a + max;        // ClassCastException falls a nicht konvertierbar,
                                   // NullPointerException falls a undefiniert
    return sum;                    // implizite Konvertierung (boxing) zu Integer
 }


Statisch, implizit, stark: OCAML

 let myAdd a b =                (* Wir definieren eine Funktion, die zwei nicht näher bezeichnete Werte nimmt *)
   a +. b;;                     (* +. ist der Additionsoperator für Gleitkommazahlen *)
                                (* val myAdd : float -> float -> float = <fun> *)
                                (* Ocaml hat von selbst erkannt, dass diese Funktion zwei Gleitkommazahlen erwartet und eine zurückgibt *)
 myAdd 1 2.5;;                  (* Addiere zwei Zahlen, aber eine davon ist eine Ganzzahl *)
                                (* '''Compilerfehler:''' This expression has type int but is here used with type float *)
 myAdd (float_of_int 1) 2.5;;   (* Addiere zwei Zahlen, wandle Ganzzahl zuvor in Gleitkommazahl um, ergibt 3.5 *)

 let x = object method a = 10 end;;               (* definiere ein Objekt mit einer Methode a, die den Wert 10 zurückgibt *)
 let y = object method a = 10 method b = 11 end;; (* definiere ein Objekt mit zwei Methoden a und b, die jeweils unterschiedliche Ints zurückgeben. *)
 let l = [x;(y :> <a:int>)];;                     (* speichere x und y in einer Liste. y wird dabei in den Typ von x umgewandelt *)


Statisch und dynamisch, explizit, stark: Pascal

 var a,b : integer;
       c : char;
 begin
   a := 33;                        { Weise a den ganzzahligen Wert 33 zu. }
   c := chr(a);                    { Weise c den Buchstaben zu, der dem ASCII-Wert von a entspricht. }
   b := ord(c);                    { Weise b den ASCII-Wert von c zu. }
   writeln(b, c);                  { Gibt "33 !" aus. }
   b := a / 2;                     { Laufzeitfehler, da die Division keinen ganzzahligen Wert ergibt. }
 end


Dynamisch, implizit, schwach: Perl

 $x = 1.5;                         # x ist nun die Zeichenkette "1.5"
 $x = $x + "4-2";                  # x ist nun "5.5" da nur 4 addiert wurde
 $x = $x + "text";                 # x ist unverändert, da "text" nicht mit einer Zahl beginnt


Dynamisch, implizit, schwach: PHP

 $x = 1;                           // Weise x explizit den Wert 1 zu.
 $x = 2 + "42";                    // Addiere eine Zahl und eine Zeichenkette,
                                   // wandle dazu die Zeichenkette in eine Zahl um
                                   // $x ist jetzt 44
 $x = 2 + "Ich bin keine Zahl";    // Wie oben, ungültige Zeichenkette wird zu 0 umgewandelt
                                   // $x ist jetzt 2


Dynamisch, implizit, stark: Python

 x = 1                             # Weise x explizit den Wert 1 zu.
 x = 2 + "42"                      # Addiere eine Zahl und eine Zeichenkette, Laufzeitfehler ("TypeError")
 x = 2 + int("42")                 # Addiere eine Zahl und eine Zahl, die aus einer Zeichenkette erstellt wird
                                   # x ist jetzt 44
 x = 2 + int("Ich bin keine Zahl") # Laufzeitfehler ("ValueError")


Statisch und dynamisch, explizit, stark: Pike

 int x = 1;                        // Definiere x als int und weise explizit den Wert 1 zu.
 x = 2 - "42";                     // Subtrahiere eine Zeichenkette von einer Zahl, Kompilier- oder Laufzeitfehler ("Bad argument 2 to `-.")
 x = "42";                         // Weise x den wert "42" zu. Kompilierfehler ("Bad type in assignment")
 x = 2 + (int)"42";                // Addiere eine Zahl und eine Zahl, die aus einer Zeichenkette erstellt wird
                                   // x ist jetzt 44
 x = 2 + (int)"Ich bin keine Zahl" // Ungültige Zeichenkette wird zu 0 umgewandelt
                                   // x ist jetzt 2

Siehe auch

Literatur

  • Luca Cardelli: Type Systems. In: Allen B. Tucker (Hrsg.): The Computer Science and Engineering Handbook. CRC Press, Boca Raton FL 1997, chapter 103, S. 2208–2236.
  • Benjamin Pierce: Types and Programming Languages. MIT Press, 2002
  • Robert Harper: Practical Foundations for Programming Languages 2nd Edition. Cambridge University Press, 2016 (online [PDF; 1,9 MB]).

Weblinks