© SkillUp/Shutterstock.com
Einen Parser für mathematische Ausdrücke erstellen

Mathematik mit TypeScript


Eine neue Programmiersprache lernt man am besten mit Hilfe von konkreten Anwendungen. Wir werden TypeScript in diesem Artikel objektorientiert angehen und einen Parser für mathematische Ausdrücke implementieren. Hier gilt es, Zeichenketten auszuwerten, einen Musterabgleich durchzuführen und Berechnungen vorzunehmen. Starten Sie durch mit TypeScript und nutzen Sie die Ausdrucksstärke der Programmiersprache.

Die Eignung und Stärke einer Programmiersprache kann man beispielsweise daran ausmachen, wie gut es gelingt, einen Sachverhalt bzw. eine Problemstellung damit darzustellen. Welche Sprachmerkmale sind vorhanden, um den Quellcode gut zu strukturieren, Mustervergleiche vorzunehmen oder ein objektorientiertes Modell zu erstellen? Kann man eigene Datentypen definieren, damit die Ausdrucksstärke des Quellcodes steigt? Nicht zuletzt bestimmt sich die erreichte Qualität des Quellcodes auch durch die Lesbarkeit und Fähigkeit zur Wartung. Diese Kriterien wiederum haben Einfluss auf die Effizienz des Entwicklers und die Produktivität des gesamten Projektes.

In diesem Artikel wollen wir die Eignung von TypeScript zur Formulierung von anspruchsvollen Algorithmen testen. Wie gut gelingt es, einen fachlich komplexen Sachverhalt und dessen Lösung in TypeScript abzubilden? Dazu haben wir eine bekannte Problemstellung ausgewählt, nämlich die Entwicklung eines Parsers für mathematische Ausdrücke. Parser sind vereinfachte Compiler, mit deren Hilfe Eingabedaten analysiert, interpretiert und in eine für den Rechner verständliche Form gebracht werden können. Mit anderen Worten: Der Computer wird in die Lage versetzt, nicht nur mit einer vorgegebenen mathematischen Gleichung zu rechnen, sondern diese auch inhaltlich zu „verstehen“. In diesem Fall kann mit dem Gleichungsterm eigenständig gerechnet werden. Beispielsweise kann eine mathematische Funktion in Form eines Textes (Zeichenkette) eingegeben werden. Der Parser ist in der Lage, den Aufbau und die Zusammenhänge dieser Gleichung zu erkennen und damit zu arbeiten, beispielsweise den Wert der Gleichung für einen bestimmten Parameter zu errechnen, die Gleichung symbolisch zu differenzieren oder das Integral zu bilden. Ein mathematischer Parser ist also ein einfaches Computer-Algebra-System (Kasten: „Computer-Algebra-System“).

Computer-Algebra-Systeme

Mittels eines Computer-Algebra-Systems (CAS) können mit dem Rechner mathematische Aufgaben gelöst werden. Dabei können nicht nur Zahlen- bzw. Näherungswerte für bestimmte Fragestellungen ermittelt werden, sondern es ist auch möglich, mit symbolischen Ausdrücken (Funktionen, Variablen, Vektoren und Matrizen) zu rechnen und diese als Ergebnis auszugeben. Zu den Standardaufgaben eines CAS gehören beispielsweise die Vereinfachung von Termen, das Lösen von Gleichungen oder Gleichungssystemen sowie das Differenzieren und Integrieren mathematischer Funktionen. Die Entwicklung eines CAS ist nicht trivial. Je nach gewünschtem Funktionsumfang sind die verschiedensten mathematischen Verfahren und Methoden zu implementieren. Der Entwickler muss nicht zwangsläufig für seine speziellen Anwendungen ein eigenes CAS entwickeln, sondern findet eine Reihe von freien und kommerziellen Bibliotheken, auf deren Funktionsumfang er im Bedarfsfall zurückgreifen kann. Bekannte Beispiele kompletter mathematischer Anwendungen mit integrierten CAS sind Mathematica und Maple.

Die Systematik, die hinter einem solchen Parser steckt, können wir auch für viele andere Einsatzzwecke anwenden, zum Beispiel bei der Text-, Sprach- und Bildanalyse.

In diesem Artikel werden wir uns in einem ersten Schritt die Grundlagen der Programmiersprache TypeScript ansehen, die für die technische Umsetzung des Algorithmus notwendig sind. Danach geht es um das fachliche Verständnis des Parsers, d. h., wir erstellen ein Konzept für den zugrundeliegenden Algorithmus. Dann sind wir soweit und können den Quelltexteditor und den Transpiler anwerfen und den Algorithmus in TypeScript schrittweise codieren.

Wichtige Sprachelemente für Algorithmen in TypeScript

Wir benötigen u. a. die nachfolgenden Sprachmerkmale, um den Algorithmus auf eine einfache und möglichst elegante Art und Weise umzusetzen. Beginnen wir mit den Aufzählungstypen (Enums). Aufzählungen sind eine der wenigen Funktionen von TypeScript, bei denen es sich nicht um eine Erweiterung von JavaScript auf Typebene handelt. Mit Enums [1] kann ein Entwickler eine Reihe benannter Konstanten definieren. Die Verwendung von Aufzählungen kann es einfacher machen, eine Reihe unterschiedlicher Fälle zu modellieren. TypeScript bietet sowohl numerische als auch stringbasierte Aufzählungen. Eine Aufzählung wird mit dem Schlüsselwort enum definiert, zum Beispiel:

enum Direction { Up = 1, Down, Left, Right, }}

Wir haben eine numerische Aufzählung, beginnend mit Up = 1, initialisiert. Alle folgenden Elemente werden automatisch inkrementiert. Down hat somit den Wert 2, Left den Wert 3 und Right den Wert 4. Lassen wir die Initialisierung weg, dann starten wir mit Up = 0 und den darauffolgenden Werten.

Als nächstes betrachten wir die Möglichkeiten der objektorientierten Programmierung. Herkömmliches JavaScript verwendet Funktionen und prototypbasierte Vererbung, um wiederverwendbare Komponenten aufzubauen. Für Entwickler, die sich mit einem objektorientierten Ansatz auskennen, bei dem Objekte aus Klassen erstellt werden, ist dieses Vorgehen möglicherweise etwas umständlich. Seit ECMAScript 2015 (ECMAScript 6), können wir in JavaScript mit einem objektorientierten klassenbasierten Ansatz arbeiten. In TypeScript können Entwickler diese Techniken stets verwenden. Der Transpiler kompiliert den Quellcode in eine JavaScript-Version, die für alle gängigen Browser und Plattformen geeignet ist. Dazu beachten Sie folgende Anmerkungen:

  • Klassendefinition: Erfolgt mit dem Schlüsselwort class, zum Beispiel wie in Listing 1.

  • Abstrakte Klassen: Werden durch das Schlüsselwort abstract eingeleitet, zum Beispiel wie in Listing 2.

  • Zugriffsberechtigung: TypeScript unterscheidet private, protected und public. Objekte, die als private definiert wurden, sind nur innerhalb der definierenden Klasse gültig; protected erlaubt den Zugriff in der betreffenden Klasse und in Klassen, die davon abgeleitet wurden, und public ermöglicht einen Zugriff von außen. Achtung: Wenn Sie keine Zugriffsberechtigung angeben, wird automatisch public angewendet.

  • Konstruktor: Wird mit dem Schlüsselwort constructor definiert (Listing 3).

  • Zugriff auf Variablen der Klasse: Dazu wird this, zum Beispiel in der Form this.id = _id, innerhalb des Konstruktors verwendet.

  • Methoden: Eine Methode wird in der Form NamederMethode(Argumente): Rückgabewert {….} definiert. Listing 4 zeigt ein Beispiel.

  • Abstrakte Methoden: Werden auch über das Schlüsselwort abstract definiert. Sie benötigen in der betreffenden Klasse keine Implementierung.

  • Vererbung: Die Ableitung (Vererbung) der Klassen geschieht über das Schlüsselwort extends, zum Beispiel in dieser Form: class Motorrad extends Kraftfahrzeug {…}

  • Überschreiben von Methoden: In abgeleiteten Klassen können Methoden überschrieben werden, indem sie auf die gleiche Art und Weise definiert werden. Achtung: Eine Besonderheit besteht darin, dass die Methode der Basisklasse explizit mittels des Schlüsselwortes super aufgerufen werden muss.

Wir haben uns explizit nur die Kernfeatures der objektorientierten Programmierung mit TypeScript angesehen. Weitere wichtige Features für die Nutzung in der Praxis sind zum Beispiel die Verwendung von readonly, um Variablen nur mit einem Lesezugriff zu versehen oder static, um statische Methoden oder Felder zu erstellen. Eines wird deutlich: Die Arbeit mit Klassen in TypeScript ist deutlich näher an den Sprachen C# oder Java als an JavaScript und das erlaubt uns eine sehr typsichere Programmierung sowie eine ...

Neugierig geworden? Wir haben diese Angebote für dich:

Angebote für Gewinner-Teams

Wir bieten Lizenz-Lösungen für Teams jeder Größe: Finden Sie heraus, welche Lösung am besten zu Ihnen passt.

Das Library-Modell:
IP-Zugang

Das Company-Modell:
Domain-Zugang