Anzeige
Anzeige
How-To
Artikel merken

ReasonML: Der mächtige JavaScript-Transpiler im Check

Wer einen Transpiler für JavaScript sucht, stößt neuerdings auch auf ReasonML. Wie schlägt sich die junge Sprache im Vergleich zu Elm – und wie gelingt der Einstieg? Ein Überblick.

Von Marco Emrich
9 Min. Lesezeit
Anzeige
Anzeige

Entwicklerteams greifen gerne auf Transpiler-Sprachen wie TypeScript, ReasonML und Elm ­zurück. (Foto: Shutterstock / RossHelen)

In Web-Umgebungen geht ohne die Programmiersprache ­JavaScript gar nichts. Dementsprechend beliebt ist sie auch als Zielsprache für Transpiler – also Compiler, die den Quellcode einer Programmiersprache in den Quellcode einer anderen Programmiersprache übersetzen. Entwicklerteams greifen gerne auf Transpiler-Sprachen wie TypeScript, ReasonML und Elm ­zurück, weil diese Vorteile wie die statische Typisierung, die ­Nutzung moderner Sprach-Features oder eine bessere Syntax für die ­funktionale Programmierung mitbringen.

Anzeige
Anzeige

Insbesondere die statische Typisierung sowie die ­funktionale Programmierung sind hier besonders interessant. Statische ­Typisierung kann dabei helfen, Fehler frühzeitig aufzu­decken, und Compiler ­können statisch typisierten Code oft besser ­optimieren. Zudem bieten integrierte Entwicklungsumgebungen (IDE) eine bessere Autovervollständigung an. Funktionale Programmierung hilft mit Konzepten wie Immutability, also unveränderlichen Werten, und dem statuslosen Programmiermodell, Code zu entwickeln, der sich leichter lesen und einfacher warten lässt. An dieser ­Stelle lohnt sich der Blick auf die Vor- und Nachteile der einzelnen Transpiler-­Sprachen, um Entwicklern eine Ent­scheidungshilfe zu geben, für welches Projekt sich welche Sprache eignet.

Elm orientiert sich eher an Haskell und bringt weitere Konzepte der funktionalen Programmierung mit. ­Entwickler ­schätzen die Transpiler-Sprache vor allem für die User-Experience und den Mangel an historischen Altlasten. Zudem über­zeugen die Standard­bibliotheken mit durchdachten Konzepten und ­erstaunlicher Konsistenz. Dass Elm seine Sprach­konzepte mit einem Framework zur Web-Entwicklung kombiniert, ist ein ­weiterer Vorteil. Die sogenannte „Elm-Architektur“ gilt als vorbildlich: Das State-Management hat sich auch außerhalb der Community durchgesetzt und diente beispielsweise als Vorbild für die JavaScript-Bibliothek Redux. Das Standardbeispiel aus dem Elm-Tutorial ist ein Zähler, der sich per Knopfdruck erhöhen oder verringern lässt:

Anzeige
Anzeige

main =
Browser.sandbox { init = 0, update = update, view = view }
type Msg = Increment | Decrement
update msg model =
case msg of
Increment ->
model + 1

Anzeige
Anzeige

Decrement ->
model - 1

view model =
div []
[ button [ onClick Decrement ] [ text "-" ]
, div [] [ text (String.fromInt model) ]
, button [ onClick Increment ] [ text "+" ]
]

Anzeige
Anzeige

Allerdings ist die Interaktion mit bestehenden JavaScript-­Bibliotheken und Frameworks für Elm-Apps eher schwierig. Das hat beispielsweise dazu geführt, dass sich der „Backend-as-a-­Service“-Anbieter Darklang von Elm abgewandt und seine ­komplette Plattform nach ReasonML konvertiert hat.

ReasonML – Eine Wiederentdeckung

ReasonML wiederum stammt aus der Feder von React-Framework-­Erfinder Jordan Walke. Obwohl die Sprache erst seit 2016 existiert, kümmert sich mittlerweile ein ganzes Team bei Facebook um die Weiterentwicklung der Transpiler-Sprache. ­Daneben sind auch der Finanzriese Bloomberg sowie ehrenamtliche Enthusiasten aus aller Welt beteiligt. So verwundert es wenig, dass das Projekt auf GitHub fast 9.000 Sterne und 150 Beteiligte hat. Es schneidet regelmäßig gut ab, wenn es um die Entwicklerzufriedenheit geht, wie die Abbildung zeigt.

Die jährliche „State of JavaScript“-Umfrage beweist es: Entwickler sind zufrieden mit ReasonML als Transpiler-Sprache.

Kurioserweise ist ReasonML aber gar keine neue Sprache. Das zugrunde liegende OCaml gibt es bereits seit 1996. Walke fiel auf, dass OCaml sehr gut zu React passt, die ungewohnte ­Syntax jedoch viele Frontend-Entwickler abschreckt. So kam er auf die Idee: Wie wäre es, der Sprache OCaml eine Syntax zu verleihen, die es JavaScript-­Entwicklern ermöglicht, sich schnell zurechtzufinden? So lassen sich mit ReasonML die Konzepte einer ausgereiften und funktionalen Programmiersprache mit dem beliebten Hindley-Milner-Typsystem in kürzester Zeit ins Frontend übertragen. ReasonML-Code ist immer zu 100 Prozent typisiert – auf eine Hintertür wie TypeScripts any oder ts-ignore wurde bewusst verzichtet. Das erspart auch ein Werkzeug zur Type-­Coverage-Analyse.

Anzeige
Anzeige

Und dies funktioniert erstaunlich gut: ReasonML ist für ­JavaScript- und TypeScript-Entwickler relativ einfach zu er­lernen. Die Sprache bringt ein hervorragendes und lückenloses Typ­system mit, eine hohe Performance und Konzepte aus der funktio­nalen Programmierung. Für eine derart junge ­Sprache steht außerdem schon ein verhältnismäßig ausgefeiltes Tooling zur Verfügung.

Reasons Transpiler, der sogenannte ­„BuckleScript-Transpiler, kennt immer den Typ jedes Wertes. Meistens kann ­BuckleScript einen Typ auch ohne Annotation bestimmen. Sollte das nicht möglich sein, bricht die Kompilierung mit einem Fehler ab. Eine gezielte Annotation ist in wenigen Fällen nötig. BuckleScript stellt so die Disziplin bei der Typisierung sicher und fordert gleichzeitig weniger Typannotationen als TypeScript. Folgende Probleme kann TypeScript beispielsweise nicht erkennen:

type Person = {name: string, vorname: string};
let begruesse1 = (p: Person) => 'Hallo {p.vorname}';
let begruesse2 = (p: {vorname: string}) => 'Hallo {p.vorname}';
let begruesse3 = (p: any) => 'Hallo {p.vorname}';
let begruesse4 = (p) => 'Hallo {p.vorname}'; // Fehler bei --noImplictAny

Anzeige
Anzeige

begruesse1(27); // => Fehler erkannt!
begruesse2(27); // => Fehler erkannt!
begruesse3(27); //OK, sollte aber Fehler sein
begruesse4(27); //OK, sollte aber Fehler sein

Für ReasonML ist das kein Problem, wie Abbildung 2 zeigt. ­Generell erkennt Reason viele Typprobleme, für die TypeScript zusätzliche Annotationen benötigt. Eine IDE, die Reason per Plugin unterstützt, kann schon ­während der Entwicklung aktuelle Typen einblenden, ohne dass eine gezielte Annotation nötig ist. Die kleinen grauen Typannotationen über den Zeilen hat VS Code generiert und eingeblendet: In Zeile 6 erkennt die Typinferenz eine Person anhand der Struktur. Der Typ der Funktionen begruesse1 und begruesse4 ist jeweils person => string, das Argument ist also eine Person und der Rückgabewert vom Typ String. Funktion begruesse1 verfügt über eine explizite Annotation für Person. Zeile 10 zeigt mit begruesse4, dass das gar nicht nötig ist, und die Typinferenz es auch ohne Annotation erkennt. So kann Reason den Fehler in Zeile 19 problemlos erkennen.

Um die Fehlererkennung eines Typsystems zu maximieren, ist es wichtig, Typen so zu designen, dass sich gültige ­Werte im Typsystem darstellen lassen, das Typsystem ungültige aber ablehnt. Im Domain-Driven-Design nennt man das Ver­meidung von Invarianten. Natürlich lassen sich leicht Prüfungen ­programmieren, die Invarianten zur Laufzeit erkennen. Ein gutes Typsystem ermöglicht das aber bereits zur Compile-Zeit.

Anzeige
Anzeige

Ein Beispiel dafür sind Variant Types (Discriminated Union Types in TypeScript).

// VariantExample.re

type farbe = Gruen | Blau | Gelb | Rot;

Anzeige
Anzeige

type form =
| Kreis({radius: int})
| Rechteck({
hoehe: int,
breite: int,
});

let korrekt1 = Kreis({radius: 10});
let korrekt2 = Rechteck({hoehe: 5, breite: 4});

let fehlerErkannt1: form = Rechteck({radius: 10});
let fehlerErkannt2 = Rechteck({radius: 10});

Anzeige
Anzeige

Im Falle von Farbe sieht der Variant Type noch wie ein einfacher Enum aus. Spätestens Form dürfte klarstellen, dass ­unterschiedliche Varianten von Formen auch unterschiedliche Daten mitbringen können. Eine Form ist entweder ein Kreis oder ein Rechteck. Beide Varianten haben unterschiedliche Daten: Ein Kreis hat den Radius , ein Rechteck Höhe und Breite. Egal, ob mit fehlerErkannt1 oder ohne Typannotation fehlerErkannt2, ReasonML kann die Fehler erkennen.

In TypeScript funktioniert das ähnlich gut, allerdings ist die Schreibweise ein wenig umständlicher und der Compiler erkennt Fehler nur bei expliziter Typannotation:

// VariantExample.ts

type Kreis = {
type: "Kreis"
radius: number
};
type Rechteck = {
type: "Rechteck"
hoehe: number
breite: number
};

type Form = Kreis | Rechteck;

let fehlerErkannt: Form = {
type: "Rechteck",
radius: 10
};
let fehlerNichtErkannt = {
type: "Rechteck",
radius: 10
};

Funktionale Programmierung

JavaScript hat seine Wurzeln in der funktionalen ­Programmierung. Mit EcmaScript2015 und Arrow-Funktionen ist der FP-Ansatz in der ­JavaScript-Community noch einmal deutlich populärer geworden. ReasonML geht auch hier noch ein paar Schritte weiter und bietet viele funktionale Konzepte an. Beispielsweise stellt es mit dem Pipe-­Operator |> ein Sprachfeature zur Verfügung, das für JavaScript zumindest schon als Proposal existiert.

Die Komposition von Funktionen ist eines der heraus­ragenden Features der funktionalen Programmierung. Bereits ­EcmaScript 5 hat Higher-Order-Funktionen wie Map und ­Filter eingeführt, die andere Funktionen als Parameter entgegen­nehmen. Mit ES2015 kamen die Arrow-Funktionen als eine wesentliche Syntax-Verbesserung hinzu.

Mit Higher-Order-Funktionen auf dem Array-Objekt lassen sich hervorragend funktionale Pipelines bauen. Aus einer Liste von Wörtern soll beispielsweise ein Stichwortverzeichnis erstellt werden, bei der die Wörter ihrem jeweiligen Anfangsbuchstaben zugeordnet sind. Es sollen aber nur kurze Wörter (weniger als fünf Buchstaben) berücksichtigt werden, und die Ausgabe erfolgt komplett in Großbuchstaben.

Der erste Teil, also das Filtern der kurzen Wörter und das Wandeln in Großbuchstaben lässt sich in JavaScript wie folgt erreichen:

const shortWordsUpperCase = words =>
words.filter(w => w.length < 5).map(s => s.toUpperCase());

In ReasonML sind keine Methoden auf dem Array-Objekt nötig. Stattdessen kann der Pipeline-Operator |> das Ergebnis eines Funktionsaufrufs in den nächsten packen:

let shortWordsUpperCase = words =>
words
|> Array.filter(w => String.length(w) < 5) |> Array.map(String.toUpperCase);

Der Vorteil besteht hier darin, dass das nicht nur für Array-­Methoden ­funktioniert, sondern mit beliebigen Funktionen. Entsprechend lässt sich auch der Rest der Aufgabe mit der ­Hilfe von ­Pipelines ­lösen:

let pair = (letter, words) => (letter, words);

let partitionByFirstLetter = words => {
let letters = [|"A", "B", "C", "D", "E", "F"|];
letters
|> Array.map(l => words |> Array.filter(String.startsWith(l)) |> pair(l));
};

let shortWordsUpperCaseByLetter = ws =>
ws |> shortWordsUpperCase |> partitionByFirstLetter;

Js.log(words |> shortWordsUpperCaseByLetter);

Damit ist die funktionale Programmierung in ReasonML kein radikaler Bruch zu modernem JavaScript – es wirkt eher wie eine ­konsequente Weiterentwicklung. Das zeigt sich auch an weiteren funktionalen Features wie dem Destructuring und ­Pattern-Matching, dem Auto-­Currying und der Immutability per Default.

Interaktion mit JavaScript

Auch die Interaktion mit bestehendem JavaScript-Code ist für ­ReasonML kein Problem. Mithilfe einer Reihe von Anno­tationen kann externer JavaScript-Code nachtypisiert werden. Bei ­Bibliotheken mit hochdynamischem Verhalten ist das jedoch nicht immer einfach. Im folgenden Beispiel wird die Button-­Komponente aus dem CSS-­Framework Ant-Design (etwas verkürzt) eingebunden:

[@bs.module "antd/lib/button"] [@react.component]
external make:
(
~onClick: ReactEvent.Mouse.t => unit=?,
~id: string=?,
~className: string=?,
~children: React.element=?,
~size: [@bs.string] [ | 'default | 'middle | 'small]=?,
~_type: [@bs.string] [| 'primary | 'dashed | 'danger] =?
) => React.element = "default";

Bei TypeScript ist dies einfacher, nicht zuletzt, weil das ­Repository DefinitelyTyped praktisch für sämtliche relevanten JavaScript-­Bibliotheken TypeScript-Deklarationen zur Verfügung stellt.

Die hervorragende Kompatibilität zu JavaScript ist das ­herausragende Feature von TypeScript. Selbst komplexe Typ-­Situationen lassen sich mit TypeScript abbilden, etwa ­Variadic Functions, also Funktionen mit unterschiedlich vielen Argumenten oder Funktionen mit variierendem Rückgabewert. ReasonML holt hier ein wenig auf. Die Anzahl der Bindings auf ­­redex.github.io wächst stetig und die Erstellung eigener Bindings ist zwar nicht einfach, gelingt aber mit neueren BuckleScript-Versionen immer besser.

Tooling Für Einsteiger

Das Tooling von ReasonML ist für eine so junge Sprache erstaunlich gut. Die BuckleScript-Plattform bringt einen ­Generator zum Erstellen eigener Projekte mit, ähnlich wie der Generator ­„Create-React-App“. Anhand des Theme-Parameters können Entwickler bestimmen, ob sie etwa ein reines Reason- oder ein ReasonReact-Projekt erstellen wollen. Das ist besonders für Einsteiger hilfreich, die sich nicht mit Compiler-­Einstellungen und passender Webpack-Konfiguration beschäftigen wollen. Ist ­NodeJS installiert, steht nach nur wenigen Befehlen ein lauf­fähiges Projekt bereit:

npx -p bs-platform bsb -init myproject -theme react-hooks
cd myproject
npm i

Die Plattform bringt mit rmft (kurz für „reformat“) auch gleich ein Formatierungswerkzeug ähnlich wie Prettier für JavaScript mit, sodass überflüssige Syntax-Diskussionen gar nicht erst aufkommen. Eine Reihe von Plugins für bekannte IDE wie VS Code, Intellij oder Emacs vervollständigen das gelungene Tool-Angebot.

Fazit

Die Entscheidung für eine Transpiler-Sprache hängt vom Projekt ab. Wer Wert auf die maximale Kompatibilität mit JavaScript legt oder bestehenden JavaScript-Code mit einem Typsystem anreichern will, sollte sich TypeScript anschauen. Eventuell sind selbst geschriebene Bibliotheken im Einsatz, für die es keine bestehenden Typdefinitionen gibt und die gleichzeitig eine bunte Palette von schwer zu typisierenden API bereitstellen. Hier wäre eine nachträgliche Typisierung mit ReasonML zu aufwendig.

Entwickler, deren Projekte keinerlei Abhängigkeiten zu ­existierendem JavaScript-Code haben und auch keine Bibliotheken aus dem JavaScript-Ökosystem benötigen, können bedenkenlos zu Elm greifen. Wer die etwas steilere Lernkurve nicht scheut, findet darin eine durchdachte funktionale Sprache, ein hervorragendes Typsystem und eine umfangreiche Standardbibliothek.

ReasonML befindet sich zwischen den beiden Extremen. ­JavaScript-Bibliotheken, die nicht allzu viele Sonderfälle mitbringen, sind leicht nachzutypisieren. Mit etwas Zusatzaufwand lassen sich auch komplexere Fälle berücksichtigen. Dank der an ­JavaScript angelehnten ­Syntax ist ­ReasonML für eine ­funktionale Sprache sehr einsteiger­freundlich. Der Hauptvorteil ist ein Typ­system, das trotz überschaubarer Komplexität ein gutes Kosten-/Nutzen-Verhältnis mitbringt. Die konsequente Ausrichtung auf funktionale ­Programmierung mit der Möglichkeit, notfalls auf klassische Konzepte zurückzu­greifen, ermöglicht das Schreiben von verständlichem Code. Der extrem schnelle Compiler und das gut ausgebaute Tooling ­sorgen für eine Entwicklungserfahrung auf hohem Niveau, das fast an das von Elm heranreicht. Dabei ist es problemlos möglich, auf bestehende JavaScript-Bibliotheken und ­Frameworks zurückzugreifen.

Mehr zu diesem Thema
Fast fertig!

Bitte klicke auf den Link in der Bestätigungsmail, um deine Anmeldung abzuschließen.

Du willst noch weitere Infos zum Newsletter? Jetzt mehr erfahren

Anzeige
Anzeige
Schreib den ersten Kommentar!
Bitte beachte unsere Community-Richtlinien

Wir freuen uns über kontroverse Diskussionen, die gerne auch mal hitzig geführt werden dürfen. Beleidigende, grob anstößige, rassistische und strafrechtlich relevante Äußerungen und Beiträge tolerieren wir nicht. Bitte achte darauf, dass du keine Texte veröffentlichst, für die du keine ausdrückliche Erlaubnis des Urhebers hast. Ebenfalls nicht erlaubt ist der Missbrauch der Webangebote unter t3n.de als Werbeplattform. Die Nennung von Produktnamen, Herstellern, Dienstleistern und Websites ist nur dann zulässig, wenn damit nicht vorrangig der Zweck der Werbung verfolgt wird. Wir behalten uns vor, Beiträge, die diese Regeln verletzen, zu löschen und Accounts zeitweilig oder auf Dauer zu sperren.

Trotz all dieser notwendigen Regeln: Diskutiere kontrovers, sage anderen deine Meinung, trage mit weiterführenden Informationen zum Wissensaustausch bei, aber bleibe dabei fair und respektiere die Meinung anderer. Wir wünschen Dir viel Spaß mit den Webangeboten von t3n und freuen uns auf spannende Beiträge.

Dein t3n-Team

Melde dich mit deinem t3n Account an oder fülle die unteren Felder aus.

Bitte schalte deinen Adblocker für t3n.de aus!
Hallo und herzlich willkommen bei t3n!

Bitte schalte deinen Adblocker für t3n.de aus, um diesen Artikel zu lesen.

Wir sind ein unabhängiger Publisher mit einem Team von mehr als 75 fantastischen Menschen, aber ohne riesigen Konzern im Rücken. Banner und ähnliche Werbemittel sind für unsere Finanzierung sehr wichtig.

Schon jetzt und im Namen der gesamten t3n-Crew: vielen Dank für deine Unterstützung! 🙌

Deine t3n-Crew

Anleitung zur Deaktivierung
Artikel merken

Bitte melde dich an, um diesen Artikel in deiner persönlichen Merkliste auf t3n zu speichern.

Jetzt registrieren und merken

Du hast schon einen t3n-Account? Hier anmelden

oder
Auf Mastodon teilen

Gib die URL deiner Mastodon-Instanz ein, um den Artikel zu teilen.

Anzeige
Anzeige