Sprachkonzepte


... [ Seminar Programmiersprachen und Sprachsysteme ] ... [ Google's Dart ] ... [ Inhaltsverzeichnis >> ] ...

Übersicht: Sprachkonzepte


Objektorientiert

Wie fast alle neuen Programmiersprachen ist Dart auch Objektorientiert. Objektorientiert heißt, dass praktisch der volle Umfang an OOP Konzepten benutzbar sind. Das beinhaltet natürlich auch die Vererbungen. Dart ist eine rein Objektorientiere Sprache. D.h. dass genau wie in JavaScript, Smalltalk, Ruby, Scala usw. auch in Dart alles ein Objekt ist. Somit ist auch so was wie das möglich, da die Strings und Doubles auch Objekte sind:
int nummer = 42.7.round(); // 43
Oder:
List str = "Dart;JS;C++;Java".split(";");
Es gibt kein Autoboxing und keine versteckte Typumwandlungen wo man sich ein String in ein Int umwandelt oder ein Array in ein String. So weiß man immer was Syntaktisch mit den Objekten passiert. Folgender Code funktioniert zum Beispiel nicht in Dart:
var IntOrStr = 3 + "4";
In Dart wird bei den Code oben ein Fehler entstehen (unabhängig vom Runtime Mode), da (in diesen Fall) der +-Operator auf der rechten Seite ein num Objekt erwartet, was entweder ein Int oder ein Double ist. In JavaScript hätte diese Zeile die Variable "test" zu String gemacht und "34" gespeichert, was auch nicht offensichtlich ist, da man genauso gut ein Integer mit der Zahl 7 erwarten könnte.
[ nach oben ]



Klassen

Dart ist eine Objektorientierte Klassen-basierte Sprache. Somit gibt es in Dart auch endlich Klassen die ähnlich wie in Java oder C# funktionieren. Jede Klasse definiert implizit auch ein Interface.

Beispiel

class Apple
{
  String type;
  String color;
  Apple (this.type);
  
  getInfo () {
    return '$color $type'
           ' apple';
  }
}

var apple = new Apple('Mac');
apple.color = 'green';
window.alert(apple.getInfo()); // "green Mac apple"
Hier wird eine einfache Klasse "Apple" definiert. Sie benutzt die vereinfachte Syntax für die Konstruktoren, hat zwei (public) Variablen und eine (public) Methode "getInfo" die ein String zurückgibt.
In Dart gibt es auch eine Zugriffs Kontrolle wie in Java mit public, private und implizit protected.

Um "private" Elemente in einer Klasse zu definieren muss man bei den Namen ein '_' machen. Zum Beispiel:
class Apple {
  String _type;
  String _color;
  Apple (this.type);
  
  _getInfo() => '$color $type' ' apple';
}
Das ist die gleiche Klasse wie von oben, nur dass die zwei String Variablen und die Methode jetzt "private" sind. Natürlich ist diese Klasse so nicht mehr benutzbar. Da man kein Zugriff mehr auf die "color" Variable hat, sowie auf die Methode "getInfos". Für viele mag diese Art von Zugriffskontrolle mit einen '_' Operator ungewöhnlich sein. Doch so kann man immer sofort erkennen ob ein Element "private" ist oder nicht. Diese Idee hat Google scheinbar von ihrer Programmier-Rechtlinien in JavaScript übernommen. Da sie auch in JavaScript private Elemente mit einen Unterstrich geprefixt haben.

Wenn man den '_' Operator weglässt, ist es automatisch "public".

"protected" wie in Java gibt es in Dart nicht. Um "protected" zu simulieren muss man eine Library erstellen (mit den "library" keyword) und eine Klasse mit "private" Elementen. Damit jetzt eine Unterklasse Zugriff auf diese Elemente hat muss sie auch in der gleichen Library definiert sein. Das funktioniert, weil das "private" nicht Klassenübergreifend ist sondern Library übergreifend.

JavaScript ist zwar Objektorientiert aber pototy-basiert d.h. in JavaScript gibt es keine Klassen. Hier noch ein Beispiel wie die "Apple" Klasse in JavaScript aussehen könnte:

JavaScript Version von der Apple-klasse

function Apple (type) {
	this.type = type;
	this.color = "red";
}

Apple.prototype.getInfo = function() {
return this.color + ' ' +
this.type + ' apple';
};

var apple = new Apple('Mac');
apple.color = 'reddish';
alert( apple.getInfo() );
[ nach oben ]



Vererbung

Genau wie in Java sind auch in Dart Vererbungen der Klasen möglich. Man kann eine Einfachvererbung mit den keyword "extends" machen oder eine Mehrfachvererbung der Interfaces mit den keyword "implements".

Ein einfaches Beispiel mit Klassen und einem Interface:
 1  abstract class Shape {
 2    num perimeter();
 3  }
 4
 5  class Rectangle implements Shape {
 6    num height, width; 
 7    Rectangle(this.height, this.width);
 8    num perimeter() => 2*height + 2*width;
 9  }
10
11  class Square extends Rectangle {
12    Square(num size) : super(size, size);
13  }
Vererbungen in JavaScript sind so nicht möglich. Das Prototyp-basierte System bietet ähnliche Verfahren, wo man z.B. ein Objekt als ein Prototyp eines anderen Objektes angibt und so kann das Objekt mit den Prototyp die Methoden des anderen Objektes benutzen.
[ nach oben ]



Optionale Typen

Das Typ System in Dart ist eins der Größten Besonderheiten in Dart. In anderen Sprachen wie C++ oder Java, werden Statische Typen benutzt. Statische Typen bedeutet, dass ein Typ zur Übersetzungszeit festgelegt wird und kann später nicht geändert werden. Der Vorteil von Statischen Typen ist die Typprüfung zur Übersetzungszeit. So werden weniger Fehler im Programm entstehen, weil das Programm bei falscher Typ Benutzung gar nicht funktionieren wird. Solche Sprachen wie C#, Objectiv-C, Ruby, PHP und JavaScript benutzen wiederrum Dynamische Typen. Variablen die dynamisch Typisiert sind können ihre Typen zur Laufzeit ändern. Das macht Dynamisch Typisierte Sprachen sehr Flexibel und sehr leicht zum Benutzen, weil man sich keine großen Gedanken über den Typen machen muss. Wenn man jedoch nicht verstanden hat oder es vergessen hat wie und wann eine Sprache einen Typen plötzlich ändert und zum was, dann kann das schnell zum Semantischen Fehlern führen.

Das Konzept der Optionalen Typen hat Gilad Bracha 2001 selber vorgeschlagen unter den Namen "Pluggable Type Systems". Das Optionale Typen System bietet die Vorteile beider Systeme bzw. es geht ein Kompromiss zwischen den beiden ein. Dart besitzt also den Vorteil der Sicherheit des Statischen Typ Systems und die Flexibilität des Dynamischen Typ Systems.

Ein einfache Beispiel:

 1  class Person {}
 2
 3  class Customer extends Person
 4  {
 5     buy() { print("bought"); }
 6  }
 7
 8  main() {
 9     Person p = new Customer();
10     p.buy();
11  }
In diesem Beispiel reagiert der Compiler je nach Typen System unterschiedlich. Java ist Statisch Typisiert. Sie hätte in Zeile 10 ein Fehler ausgegeben, weil die "buy()" Methode kein Member von Person ist, da "p" von Typ "Person" ist und dieser Typ hat sich durch die Zeile 9 nicht geändert. In JavaScript würde alles normal weiter laufen, weil JavaScript Dynamisch Typisiert ist und sich der Typ von "p" von Person zu Customer zur Laufzeit geändert hätte. Und da "buy" in Customer vorhanden ist, funktioniert alles in normal. Dart dagegen hätte, je nach dem in welchen Runtime Mode er eingestellt ist, einen Fehler ausgegeben (exception zur Laufzeit) oder eine Warnung und der Code würde wie bei JavaScript normal weiter laufen.

Wichtig zu erwähnen ist, dass die Typen in Dart keinen Einfluss auf das Programm haben. Sie dienen nur als eine Dokumentation für die Entwickler, weil z.B. ein Funktions-Prototyp mit Angabe von Typen bei den Parametern und dem Rückgabewert für viel mehr Klarheit sorgt als ohne Typen.

Die Typen in Dart dienen auch für die Tools als einer Art Dokumentation. So kann der Dart Editor schon beim Typen des Codes Warnungen anzeigen.

Hier ist nochmal die "Point"-Klasse mit den Unterschied das Typen hinzugefügt würden:

Alle Typen sind Dynamic Richtig Typisiert Falsch Typisiert

 1  class Point {
 2      var x, y;
 3      Point(this.x, this.y);
 4  
 5      distanceTo(other) {
 6        var dx = x - other.x;
 7        var dy = y - other.y;
 8        return sqrt(dx * dx + dy * dy);
 9      }
10  }
11
12  main() {
13      var p = new Point(2, 3);
14      var q = new Point(3, 4);
15      print('distance from p to'
16            ' q = ${p.distanceTo(q)}');
17  }

 1  class Point {
 2      num x, y;
 3      Point(this.x, this.y);
 4  
 5      num distanceTo(Point other) {
 6        num dx = x - other.x;
 7        num dy = y - other.y;
 8        return sqrt(dx * dx + dy * dy);
 9      }
10  }
11
12  main() {
13      Point p = new Point(2, 3);
14      Point q = new Point(3, 4);
15      print('distance from p to'
16            ' q = ${p.distanceTo(q)}');
17  }

 1  class Point {
 2      var x, y;
 3      Point(this.x, this.y);
 4  
 5      void distanceTo(String other) {
 6        var dx = x - other.x;
 7        String dy = y - other.y;
 8        return sqrt(dx * dx + dy * dy);
 9      }
10  }
11
12  main() {
13      var p = new Point(2, 3);
14      String q = new Point(3, 4);
15      print('distance from p to'
16            ' q = ${p.distanceTo(q)}');
17  }

Das Erste Beispiel zeigt die Flexible Funktionsweise von Dart. Alle "var" Elemente sind "Dynamic". D.h. das Dart bestimmt selber welchen Typen die Elemente bekommen. Diese Funktionsweise unterscheidet sich nicht von JavaScript.
Der Zweite Beispiel zeigt wie man sie richtig Typisiert hätte. Das "other" in Zeile 5 könnte alles Mögliche sein. Mit "Point" weiß man das nur Point-Objekte hier passen. Da wir nicht wissen ob die Point-Klasse jetzt nur Integer bearbeiten soll oder auch Double, setzen wir "num", was einfach für eine Nummer steht. (Welche grundlegenden Typen es gibt kann man in der Tabelle weiter unten entnehmen.) In den ersten 2 Beispielen läuft alles ohne Probleme.
Der Dritte mit den Falschen Typen wird auch laufen jedoch mit mindestens 4 Warnungen (2 in Zeile 5 und jeweils 1 in Zeile 7 und 14)

Build-in Typen

Typname Wertebereich
Nummer num int or double
Integer int kein Limit
Double double 64bit IEEE 754
Boolean bool true or false
String String UTF-16
Listen List<dynamic>
Maps Map<dynamic, dynamic>
... ... ...

"num" ist eine Oberklasse von "int" und "double". Ein "int" ist nicht wie üblich in anderen Sprachen auf 32 Bit oder 64 Bit beschränkt. Hier kann ein Integer so groß sein wie viel Speicher zur Verfügung steht.
var bigInt = 346538465234590347592847298346243459756895347698465298346583746592374652378462347569234765834765947569827346583465243765923847659234765928347659567398475647495873984572947593470294387093493456870849216348723763945678236420938467345762304958724596873045876234572037862934765294365243652548673456705673465273465246734506873456729457623845623456234650457693475603768922346728346256;
print(bigInt); // In Dart VM -> OK / in JS -> Infinity
In Dart ist "true" wirklich nur "true" genau wie bei "false" d.h. dass z.B. so was nicht geht:
if (1) {
	// In Javascript -> true
} else {
	// In Dart -> false
}
In Dart geht die if-anweisung ins "else", weil die "1" kein "true" ist sondern eine "1".. bzw. eine Nummer und kein Boolean. In JavaScript ist das anderes. Ein "false" in JavaScript können folgende Sachen sein:
false, null, undefined, "", -0, 0, NaN
Das alles muss man erstmals wissen. Es ist z.B. nicht jedem offensichtlich, dass ein leerer string ein "false" ist.
[ nach oben ]



Closures

In Dart ist es möglich Closures zu benutzen. Ein Closure ist eine (Abschließende) Funktion die auf nicht-lokale Elemente zugreifen kann.

Beispiel:

 1  fibonacci() {
 2    int x = 0;
 3    int y = 1;
 4    int z;
 5
 6    return () {
 7      z = x;
 8      x = y;
 9      y = z+y;
10      return y - x;
11    };
12  }
13
14  var nextfib = fibonacci();
15  print( nextfib() ); // 0
16  print( nextfib() ); // 1
17  print( nextfib() ); // 1
18  print( nextfib() ); // 2
19  print( nextfib() ); // 3
20  print( nextfib() ); // 5
In diesen Beispiel gibt die Funktion "fibonacci" eine Funktion zurück (ohne Namen)(Zeile 6). Diese Funktion (also Zeile 6 bis 11) ist der Closure. Sie hat also den Zugriff auf den Erstellungskontext ihrer Hauptfunktion (fibonacci()), in diesen Fall auf x, y und z.
[ nach oben ]



Mixins

Mixins sind das Delta einer Klasse und seiner Elternklasse also der Unterschied zwischen diesen Klassen.

Ein einfaches Beispiel:
 1  class Point {
 2      var x, y;
 3
 4      distanceTo(other) {
 5  	  var dx = x - other.x;
 6  	  var dy = y - other.y;
 7  	  return sqrt(dx * dx + dy * dy);
 8  	}
 9  }
Das was Rot ist, ist ein Mixin. Das ist das Delta der Klasse Point und der Elternklasse Object.

Mixins werde dazu benutzt um einer Klasse Funktionalität zu geben ohne Vererbung. Dabei ist es möglich mehrere Mixins anzugeben. Wenn das der Fall ist und die Mixins die gleichen Elemente (Variablen oder Methoden) besitzen, wird das Element von der zuletzt hinzugefügten Mixin übernommen.

Um Mixins zu erstellen muss man paar Restriktionen beachten:
1. Die Mixins dürfen keine Konstrutoren haben.
2. Die Elternklasse (Superclass) der Mixins muss Object sein.
3. In der Mixin-Klasse dürfen keine "super"-Aufrufe vorhanden sein.


Wird eins dieser Punkte nicht erfüllt sein, kompiliert der Code nicht. Diese Restriktionen sind entstanden, weil die Funktionalität in Dart sehr neu ist (erst seit Anfang 2013). Laut Google werden diese Restriktionen womöglich in der Zukunft abgeschafft.

Hier noch ein einfaches Beispiel:
 1  class firstmixin {
 2    var number = 1;
 3    printNumOne() => print("FirstNum: $number");
 4  }
 5
 6  class secondmixin{
 7    var number = 2;
 8    printNumTwo() => print("SecondNum: $number");
 9  }
10
11  class Point extends Object with firstmixin, secondmixin {
12    printNumber() => printNumOne();
13  }
14
15  Point test = new Point();
16  test->printNumber(); // "FirstNum: 2"
16  test->printNumOne(); // "FirstNum: 2"
16  test->printNumTwo(); // "SecondNum: 2"
Die Mixins werden also mit den keyword "with" hinzugefügt. Das passt da man Mixins normalerweise mit Klassen benutzt die keine "ist-ein"-Beziehung haben, wie:
class AnimalLover extends Person with Dogs, Cats, Rabbits {...} 
Es wird "FirstNum: 2" ausgegeben, weil die printNumOne-Methode benutzt wird die von der firstmixin-Klasse übernommen würde. Die Point-Klasse besitzt also jetzt beide Methoden (printNumOne und printNumTwo). Die "number" Variable wird aber von der secondmixin überschrieben, weil die als letztes hinzugefügt würde. Hätte die Point Klasse jetzt auch eine "number" Variable, dann würde stattdessen diese genommen (ohne Warnungen), da die Elemente von der Point Klasse eine größere Priorität haben. Die Mixins können nur nach einen "extends" mit "with" hinzugefügt werden. Da wir in diesen Fall nicht wollen, dass Point von etwas erbt, habe wir einfach "Object" als die Elternklasse angegeben.
[ nach oben ]



Nebenläufigkeit mit Isolates

Mit Isolates ist es möglich in Dart Multithreaded zu Programmieren. Ein Isolate ist wie ein Actor. Es hat seinen eigene Heap und Stack. Man könnte sich das so vorstellen, als wenn es ein eigener Prozess mit Single-thread wäre. Die Isolates können sich also nicht direkt beeinflussen. Shared-memory ist also nicht möglich, da das zu Fehler anfällig ist und zu einem nicht übersichtlichen Quellcode führt. Stattdessen kommunizieren die Isolates über Ports (Message passing). Eine Dart App (bzw. jede ausgeführte "main") wird automatisch in einen Isolate erstellt. Jeder Isolate besitzt von Anfang an einen Empfänger-Port. Beim erstellen eines neuen Isolates (mit der "spawnFunction"-funktion), wird der Sende-Port dieses Isolates zurückgegeben. So kann die main-isolate seinen child-isolate seinen eigenen Empfänger-Port zusenden und so können beide miteinander kommunizieren. Die benötigte Library ist "dart:isolate".

JavaScript kann kein Multithreading. Sie simuliert es nur vor in dem es ihre fallback-funktionalität benutzt um es so erscheinen zu lassen als wenn die Anweisungen parallel ablaufen. Dabei läuft alles auf einen Thread. Das kompilieren von Dart zur JavaScript ist also eine Herausforderung für die Entwickler, wenn man mit mehreren Isolates in Dart arbeitet. Auf der älteren Version von Dart vor 17.01.2013 wird auch hier für die Isolates die fallback-funktionalität von JavaScript benutzt und nur Multithreading vorsimuliert. Will man hier echtes Mutlithreading in seinen JavaScript Quellcode haben, dann muss man mit der HTML5 Funktionalität "Web Workers" arbeiten die in der Library "dart:html" vorhanden ist. In den neueren Versionen ab 17.01.2013 wird Dart beim kompilieren zu JavaScript auch die Web Workers von HTML5 für die Isolates benutzen.
[ nach oben ]



Die DOM

Das eigentlich spannende an JavaScript ist, Seiteninhalte nachträglich dynamisch zu ändern. Während JavaScript die JQuery Bibliothek braucht um effektiv mit der DOM API zu arbeiten, kann Dart sehr vieles schon nativ in der Sprache.

Die alten JS Funktionen Neu in Dart
getElementsById()
getElementsByTagName()
getElementsByName()
getElementsByClassName()
querySelector()
querySelectorAll()
document.links
document.images
document.forms
document.scripts
formElement.elements
selectElement.options
query()
queryAll()

Google hat sich die JavaScript und die JQuery Funktionen angeguckt und es stark vereinfacht. Sie haben Zwei Funktionen gemacht die das alles können was all die alten Funktionen in JS auch können. Diese Zwei Funktionen "query" und "queryAll" bekommen CSS Selektoren als Parameter. So kann man quasi alle Elemente in HTML ansprechen.

Paar Beispiele:

Die alten JS Funktionen Neu in Dart
elem.getElementById('foo');
elem.getElementsByTagName('div');
elem.getElementsByName('foo');
elem.getElementsByClassName('foo');
elem.querySelector('.foo .bar');
elem.querySelectorAll('.foo .bar');
elem.query('#foo');
elem.queryAll('div');
elem.queryAll('[name="foo"]');
elem.queryAll('.foo');
elem.query('.foo .bar');
elem.queryAll('.foo .bar');
[ nach oben ]



Code Beispiele

Hier gibt es paar Quellcode Beispiele die im Dart Editor erstellt würden. Der Projekt Ordner beinhaltet auch Dateien die nur für den Dart Editor gedacht sind. Die dart-Dateien und html-Dateien funktionieren aber auch ohne den Projekt Dateien. Man kann sie also auch direkt mit der Dart VM starten.

Die Point Klasse

Ein einfaches Beispiel mit der Point Klasse.
Standalone Programm für die VM.
Download

Ein Popup Fenster

Diese Programm erstellt eine Popup-Window.
Web Anwendung.
Download

Einfaches DOM Manipulieren

Hier kann man den Inhalt eines Tags mit den Inhalt eines Input-feldes manipulieren.
Web Anwendung.
Download

Eine ToDo-Liste

Erstellung einer ToDo-Liste. Man kann TODOs hinzufügen, die einzelnen Elemente durch Anklicken wieder löschen oder mit einen Button alle Elemente löschen.
Web Anwendung.
Download

Simple Isolates

Ein einfaches Beispiel wie man 2 Isolates erstellt. Das Hauptprogramm (main) hat standardmäßig immer ein Isolate. Dazu wird noch ein "Child"-Isolate erstellt. Es wird gezeigt wie sie über Ports kommunizieren.
Standalone Programm für die VM.
Download
[ nach oben ]



Der Vergleich

Funktionalität Dart Java JavaScript
Typsystem Dynamisch (Optional) Statisch Dynamisch
First-Class Funktionen Ja Ja, mit Anonymen Funktionen Ja
Closures Ja Ja Ja
Klassen Ja, Einfach Ja, Einfach Prototyp-basierend
Interfaces Ja, Mehrfach Ja, Mehrfach Nein
Nebenläufigkeit Ja, mit Isolates Ja, mit Threads Ja, mit HTML5 web workers
[ nach oben ]





... [ Seminar Programmiersprachen und Sprachsysteme ] ... [ Google's Dart ] ... [ Inhaltsverzeichnis >> ] ...