• Noser.com
facebook
linkedin
twitter
youtube
  • NOSERmobile
    • Android
    • HTML 5
    • Hybrid Apps
    • iOS
    • Windows Phone
  • NOSERembedded
    • Medizintechnik
  • NOSERprojektmanagement
  • NOSERtesting
  • NOSERlifecycle
    • .NET Allgemein
    • Application Lifecylce Management
    • Apps
    • Architektur
    • ASP.NET
    • Azure
    • Cleancode
    • Cloud
    • Silverlight
    • Visual Studio / Team Foundation Server
    • Windows 8
    • Windows Presentation Foundation
  • NOSERinnovation
    • Big Data
    • Cloud
    • IoT
    • Operations Research
    • Augmented Reality
    • RFID, NFC, Bluetooth LE

ManuScripts: WJERT – FP mit Elm – Teil 6 – Elm rennt

30. Oktober 2016
Manuel Baumann
0
Elm, Functional Programming, HTML5, Reactive Programming, SPA

Eine Blog-Serie zum Vergleich von Elm, ReactJS und AngularJS anhand eines praktischen Beispiels.Hier geht es direkt zu [Teil 1, 2, 3, 4, 5]

Herrlich frische Bergluft bläst unseren Wandervögeln entgegen auf der Ämpächli Alp, wo Angular sich vor allem über den festen Boden unter seinen Füssen freut, sowie die Aussicht auf eine Erfrischung im Restaurant bei der Bergstation. Elm ist aber überhaupt nicht in der Stimmung, bereits eine Rast einzulegen. Sie einigen sich auf einen Treffpunkt: Hängstboden. Elm marschiert wehenden Schrittes los, mit gewölbter Brust, den Kopf der Sonne entgegengestreckt.

Zur Erinnerung: Das Endziel ist ein Anagram-Generator für die deutsche Scrabble-Community.

Etappenziel: Interaktive Benutzeroberfläche

Vorbereitung (create-elm-app)

In den letzten Monaten ist die Elm-Community sehr weit gewandert und nebst zahlreichen Editor-Plugins für die gängigen Opensource-Editoren sind andere wertvolle Tools erschienen, wie z.B. create-elm-app, welches sich webpack bedient, und damit eine “state-of-the-art” Toolchain für die Entwicklung unter elm bereitstellt.

Zur Erinnerung: Elm-Programme werden mit dem Compiler elm-make nach javascript kompiliert und das wiederum wird in eine Html-Seite eingebunden, entweder in ein Ziel-div oder fullscreen oder sogar headless (Keine Anzeige).

Der mitgelieferte elm-reactor (development web-server) reicht für die Entwicklung eigentlich aus, aber mit webpack gibt es jetzt schon solche goodies wie “hot reload” und css stylesheets können so auf die altbekannte Weise eingebunden werden, was für den Anfang auch vortheilhaft ist.

Dadurch bleibt mehr Zeit für die Sache, die Spass macht: die Programmierung mit Elm. Wer später seinen eigenen Build-Prozess einführen möchte, kann die Abhängigkeit problemlos durch das Tool entfernen lassen.

Die Installation erfolgt auf der Kommandozeile: sudo npm install -g create-elm-app

Wir fangen also nochmals ganz von vorne an mit unserer Applikation und kopieren die Models aus den vergangenen Blog-Posts bei Bedarf in unser Projekt. Zuerst aber mal eine neue elm-app erstellen:

create-elm-app /path/to/my/project/elm-anagrams

Creating elm-anagrams project...
 
Packages configured successfully!
 
Project is successfully created in `/path/to/my/project/elm-anagrams`.

cd /path/to/my/project/elm-anagrams

elm-app start

Nach kurzer Zeit erscheint:

Compiled successfully!
 
The app is running at:
 
    http://localhost:3000/
 
To create production build, run:
 
    elm-app build

und zusätzlich wird der Standard-Browser unter obiger Adresse geöffnet, wo nochmals eine zufriedenstellende Erfolgsmeldung angezeigt wird.

 

Editor: Atom, Brackets, Emacs, IntelliJ, Sublime Text, Light Table, Vim, VS Code

Hier ist die Feature-Matrix

Persönlich nutze ich im Moment Atom mit folgenden Plugins:

  • language-elm
  • elm-format
  • linter-elm-make
  • elmjutsu

Ich wechsle demnach (in einer neuen Konsole) ins elm-anagrams-Verzeichnis und starte atom .

Projekt-Struktur

elmproject

create-elm-app legt die Ordner src und tests an. Was durch elm-make kompiliert wird, landet im Verzeichnis elm-stuff. Das letzte Verzeichnis, dist, wird vom Build-Tool erzeugt. Ausserdem wird gleich auch noch die korrekte .gitgnore-Datei erzeugt, so dass wir nur noch git init && git add . && git commit -m “initial” aufrufen müssen, um unsere Resultate unter Versionskontrolle zu stellen.

Damit wir sofort loslegen können, öffnen wir die Datei src/App.elm, wo wir den folgenden Inhalt vorfinden:

module App exposing (..)
 
import Html exposing (text, div)
 

subscriptions model =
    Sub.none
 
 
update msg model =
    ( model, Cmd.none )
 
 
init =
    ( (), Cmd.none )
 
 
view model =
    div [] [ text "Your Elm App is working!" ]

Ich würde die Reihenfolge init, subscriptions, update, view bevorzugen (und rearrangiere entsprechend), aber dies bleiben die vier Bausteine, die durch die TEA (the elm architecture) vorgeschlagen werden und welche die gesamte Applikation ausmachen. Subscriptions verwenden wir vorerst nicht.

Wir erinnern uns daran, dass elm mit einem immutable (unveränderbaren) Model operiert. Das Modell wird immer an die betreffenden Funktionen übergeben und daraus wird (in den meisten Fällen) ein neues Modell resultieren.

Die folgende Grafik veranschaulicht dies und beschreibt die Funktionsweise von elm.

Update-Loop

@fredcy‘s update loop:

Here is a simplified model of how an Elm 0.17 program operates.

The update function blocks waiting for the next incoming Msg. When received it creates a new model value and commands. The view and subscriptions functions then run against the new model value, producing new Html Msg and Sub Msg values, respectively. Then update waits for the next Msg. Note that subscriptions is called after each update.

elm update loop. thanks to @fredcy!

Happy Elming!

So, hier noch der Link zum Syntax, und dann legen wir los:

Das Ziel

Unser heutiges Ziel ist es, eine einfache Eingabe-Maske zu erstellen, welche ein Textfeld enthält, welches bei Eingabe eines Zeichens den Bereich darunter entsprechend aktualisiert. Zu einem späteren Zeitpunkt befassen wir uns mit dem HTTP-Aufruf ans Backend.

UI mock

init und Model

Model the problem!

Zuerst soll man sein Problem modellieren. Welche Daten muss unser Modell enthalten, damit wir unser Ziel erreichen können?

Dazu erstellen wir im File App.elm zwei neue Typen:

  • einen mit dem treffenden Namen Model (es ist eine elm-Konvention, das Hauptmodell Model zu nennen) und
  • Anagram, welcher später ausgebaut wird.

Eine erste Version könnte folgendermassen aussehen:

type alias Anagram = String   -- bloss ein neuer Name für String, lesbarer, lässt einfachen Ausbau zu
 
type alias Model = 
  { input: String             -- der momentane String im Textfeld
  , anagrams: List Anagram    -- die Liste der aktuellen Resultate
  }

Von diesem Typ wollen wir nun eine Initialversion erstellen in der Funktion init. Diese sieht neu, mit Signatur, folgendermassen aus:

init: (Model, Cmd Msg) 
init = ( { input = "", anagrams = [] }, Cmd.none )

Wir ersetzen das bisher leere Model () vom Typ Unit durch einen record ({ input = “”, anagrams = [] }), der mit dem deklarierten Typ vollständig übereinstimmt und erst dadurch kompilieren kann. Wenn die Reihenfolge der Argumente eingehalten wird, kann das Model auch so erzeugt werden (Record-Konstruktor-Aufruf): Model “” []. Es müssen jedoch immer alle Felder initialisiert werden.

() kann als ein Tupel ohne Elemente betrachtet werden, oder ein Wert ohne Daten.

Cmd und Msg

Der Rückgabe-Wert von init ist ein Tuple tup = ( x, y ), welches an erster Stelle (fst tup) unser Modell enthält, und an zweiter Stelle (snd tup) stehen allfällig auszuführende Kommandos, die Funktionen mit Seiten-Effekten betreffen. elm kontrolliert die Aufrufe solcher Funktionen explizit, damit es nicht zu Laufzeitfehlern kommt. Der Entwickler wird dadurch gezwungen, auch den möglichen Fehlerfall im Code abzudecken. Ersichtlich wird das am Typ Task, der für den Aufruf solcher Funktionen verwendet werden muss:

type alias Task err ok = 
    Task err ok

Um einen Task erstellen zu können, müssen zwei Argumente (Funktionen) übergeben werden. Das erste behandelt den Fehlerfall, das zweite den Erfolgsfall mit dem angeforderten Resultat. Funktionen, die Seiten-Effekte aufweisen, sind z.B. Http.get oder Date.now.

Jedes Cmd spezifiziert:

  1. Auf welche Effekte man zugreifen möchte und
  2. Welche Nachrichten in die Applikation zurückgelangen.

Und obwohl wir in der Signatur von init angegeben haben, dass wir Nachrichten vom Typ Msg zurückhaben wollen, haben wir diesen Typ nirgends spezifiziert. Zeit, dies nachzuholen.

type Msg 
  = NoOp

Ich habe hier eine Nachricht deklariert, welche dem Namen nach, keine Operation zur Folge hat. Unter gewissen Umständen kann das von Nöten sein, hier wird es verwendet, um eine Fähigkeit des Typ-System von elm zu zeigen. Denn wenn nebst NoOp noch andere Nachrichten erwarten, wie z.B. wenn der Benutzer Text ins Textfeld eingibt oder löscht, dann können wir diese Information an diesen Typ anhängen, was dann so aussieht:

type Msg 
  = NoOp 
  | OnInputChanged String

Dieses Konstrukt nennt sich Union Type und ist eins der mächtigen Werkzeuge der funktionalen Programmierung. Damit können auf natürliche Weise auch komplexe Strukturen ausgedrückt werden. Union Types werden oft auch tagged unions genannt, oder auch ADTs (algebraic data types). Sie beherbergen noch sooo viel mehr, wie der Begriff algebra in ADT vermuten lässt. Dieses höchst spannende Thema muss aber noch warten, weil hier geht es um elm, und bei elm lautet das Motto: “Let’s build stuff”.

Für den Moment reicht es, zu erkennen, dass ein Wert vom Typ Msg entweder vom Typ NoOp (Nachricht ohne zusätzlichen Wert) sein kann, oder vom Typ OnInputChanged mit einem zusätzlichen “Payload” vom Typ String

view

Nun können wir eigentlich bereits unsere View erstellen. Mit voller Signatur lautet diese im Original:

view: Model -> Html Msg 
view model =
    div [] [ text "Your Elm App is working!" ]

Die view-Funktion erhält ein Model und gibt ein Konstrukt vom Typ Html Msg zurück, also Html, welches Nachrichten vom Typ Msg erzeugen kann.

Elm deckt im Modul Html grosse Teile der Html5-Funktionalität ab (mit wenigen, z.T. esoterischen Ausnahmen). Wenn ein Html-Element erzeugt werden soll, dann haben die Funktionen, die das ermöglichen, die folgende Signatur (hier am Beispiel h1):

h1 : List (Attribute msg) -> List (Html msg) -> Html msg

Nehmen wir das folgende Html an: <h1 class=”special” z-index=”50″><span>Bingo</span><span>Mania</span></h1>

Ein Html-Element hat 0-n Attribute (class, z-index), sowie eine Liste von enthaltenen, oder Kind-Elementen, wie man das von einer Baumstruktur gewohnt ist. Die Funktions-Signatur drückt genau dasselbe aus: h1 ist der Name einer Funktion mit zwei Argumenten. Beide Argumente sind Listen, die erste enthält jedoch Werte vom Typ Html.Attribute msg , die zweite Werte vom Typ Html.Html msg. Hinweis: die kleingeschriebenen Typnamen (hier msg), weisen auf Platzhalter für beliebige Typen hin, in unserem Fall heisst der konkrete Typ ja Msg (Typnamen sind immer upper case).

In elm sähe dasselbe demnach so aus: h1 [ HA.class “special”, HA.zIndex “50” ] [ span [] [text “Bingo”], span [] [text “Mania”] ]

Hinweis: Bestehendes Html kann mit dem Paket mbylstra/html-to-elm nach elm konvertiert werden. Demo

Zurück zum Anagrammerator: Hier ist der Code für die View, wie sie im Bild oben angedeutet ist, ganz ohne zusätzlichen Schnickschnack:

import Html.Attributes as HA
 
view : Model -> Html Msg 
view model =
    div []
        [ Html.h1 [] [ text "Anagrammerator" ]
        , Html.h2 [] [ text ("Input: " ++ model.input) ]
        , viewTextField model
        , viewResults model
        ]
 
 
viewTextField : Model -> Html Msg 
viewTextField model =
    div [ HA.class "text-field-area" ]
        [ Html.span []
            [ Html.label [ HA.for "input" ] [ text "Buchstaben auf Bank" ]
            , Html.input
                [ HA.id "input"
                , HA.type' "text"
                ]
                []
            ]
        ]
 
 
viewResults : Model -> Html Msg 
viewResults model =
    div [ HA.class "result-area" ]
        [ Html.h2 [] [ text "Resultate" ]
        , Html.ul [] (List.map viewAnagram model.anagrams)
        ]
 
 
viewAnagram : Anagram -> Html Msg 
viewAnagram anagram =
    Html.li [ HA.style [ (,) "font-weight" "bold" ] ] [ text anagram ]

 

  • Zuerst wird ein zusätzliches Modul für die Html-Attribute importiert und mit dem alias HA versehen, damit es später über diesen Namespace-Namen angesprochen werden kann. Wir könnten mit expose (id,style,type’) auch gleich die für unseren Code benötigten Funktionen bereitstellen, so wie es für (Html, div, text) getan wurde. Grundsätzlich sollte man aber, der Lesbarkeit zuliebe, bei Abhängigkeiten immer möglichst explizit sein. Ganz schlimm ist import Html exposing (..), weil ich zu einem späteren Zeitpunkt nicht mehr einfach herausfinden kann, welcher Code nun importiert wurde.
  • Die view-Funktion wurde aufgeteilt auf mehrere kleine Funktionen. Dies vereinfacht den Code, macht ihn wartbarer und lässt den Programmenschen sich wiederholende Muster im Code schneller erkennen.
  • In der Funktion viewTextField wird ein label und ein input vom Typ text erstellt. Man sieht auch, wie auf den äusseren div eine css-Klassen angewandt wird. Styles werden (da create-elm-app das alles für uns automatisiert), im src/main.css definiert und automatisch neu geladen.
  • Die Funktion viewResults verwendet die Funktion List.map, welche zwei Argumente verlangt: Eine Funktion, die einen Wert in einen Wert eines anderen Typs überführt (a -> b) und eine Liste von Werten, hier unsere (leere) Liste der Anagramme. Die Funktion wird dann auf jeden Wert in der Liste angewandt, was wiederum zu einer Liste führt, nur haben die Element in der neuen Liste den Typ Html Msg

preview and review

Wer seinen Browser beim Entwickeln betrachtet hat, wird feststellen, dass sich dieser bei jeder Änderung automatisch aktualisiert, ohne seinen Zustand zu verlieren. Falls die app nicht läuft, diese wieder starten, und im Browser den Output betrachten. Wer unbedingt muss, kann nun auch etwas “live” stylen, inder er main.css anpasst und speichert.

Wir stellen aber fest, dass hier noch nichts von Bedeutung geschieht. Eingaben im Textfeld werden nicht sichtbar.

Eine erste Änderung könnte nun so aussehen:

Html.input
  [ HA.id "input"
  , HA.type' "text"
  , HA.value model.input   -- setze den Wert des Input-Felds auf den Wert des Feldes `input` im Model.
  ] []

Wenn man nun versucht, ins Feld zu tippen, wird der Wert beim nächsten Zeichnen gleich wieder zurückgesetzt, was sofort stattfindet. Auch nicht so nützlich. Damit der Wert ins Model gelangt, müssen wir mit der folgenden Änderung das Ereignis abfangen und in eine Nachricht unseren Typs verwandeln:

import Html.Events as HE
 
...
  , Html.input
      [ HA.id "input"
      , HA.type' "text"
      , HA.value model.input
      , HE.onInput OnInputChanged  -- übersetze das Ereignis in eine Nachricht
      ]
      []

Eventhandler-Funktionen sind im Modul Html.Events implementiert. Darum wird dieses importiert und mit Alias versehen. Aus diesem Modul verwenden wir die Funktion

onInput : (String -> msg) -> Attribute msg

Diese Funktion hat einen Eingabe-Parameter, welcher eine Funktion ist, die einen String kriegt und einen Wert vom Typ

Durch das Hinzufügen dieses Event-Handlers wird jetzt die update-Funktion mit dieser Nachricht aufgerufen, wenn sich der Text im Feld verändert.

 

update

In dieser Funktion kommen alle Nachrichten zusammen, egal aus welcher Quelle sie stammen (Benutzereingabe, Subscriptions, Tasks) und werden ausschliesslich hier behandelt. Natürlich sind Hilfsfunktionen angebracht, nein, erwünscht, aber oft sind diese Updates gar nicht so involviert.

update : Msg -> Model -> ( Model, Cmd Msg ) 
update msg model =
    case msg of
        NoOp ->
            ( model, Cmd.none )
 
        OnInputChanged val ->
            ( { model | input = val }, Cmd.none )

Die Funktion erhält die Nachricht, sowie das aktuelle Modell und retourniert, wie die init-Methode, das neue Modell, sowie allfällige Effekt-Aufforderungen zu diesem Zeitpunkt. Hier sehen wir ein Beispiel für ein “pattern matching”. Ein case-statement muss sämtliche möglichen Belegungen des Typs behandeln, was im obigen Beispiel explizit getan wird. (_  ist ein (mit Vorsicht einzusetzender) “catch-all”-Platzhalter, welcher alle verbleibenden Fälle abdeckt)

Wenn eine NoOp-Nachricht reinkommt, tun wir gar nichts mit dem Modell und geben es ohne Effekte zurück. Im zweiten Fall, unserer OnInputChanged-Nachricht, erhalten wir einen Wert val, welches der neue Wert des Textfeldes ist, aus dem die Nachricht stammt.

Der Syntax zum Setzen eines Feldes in einem Record ist etwas speziell, d.h. nach Erläuterung macht die Schreibweise Sinn:

{ model | input = val }

Dieser Syntax bedeutet, dass ein neues Modell erzeugt wird, welches sich nur im Feld input vom alten unterscheidet. Neu hat das Feld den Wert des Nachrichten-Parameters val. Nach diesen Änderungen funktioniert das Textfeld wie erwartet und unser Etappenziel ist erreicht!

 

loop

Das Resultate-Feld ist jetzt halt leer geblieben. Wer möchte, kann im init  Werte einfüllen, und mit Listen von Werten herumspielen, oder selbst versuchen, die Liste interaktive mit Werten zu befüllen. Wir holen uns beim nächsten Mal die Werte von einem Webserver via Http-Request.

Nun aber weiterhin viel Spass beim Ausprobieren!

Elm sitzt mit gekühltem Getränk im Schatten eines grell-gelben Sonnenschirms auf der Terasse des Berggasthauses Bischofalp und fragt sich, wie lange es wohl dauern wird, bis die anderen auch hier eintreffen. Vermutlich haben sich die anderen vor der Abreise noch mit massenhaft unnötigem Material eingedeckt, bevor sie die kurze Strecke in Angriff nahmen. Sie klaubt ihr Handy aus der Hüfttasche und vertreibt die Zeit mit ein paar Spielen:

elm-package install –yes elm-community/list-extra

import List.Extra as List
import String
   
...
 
     OnInputChanged val ->
            let
                anagrams =
                    String.split "" val
                        |> List.permutations
                        |> List.map String.concat
                        |> List.unique
            in
                ( { model | input = val, anagrams = anagrams }, Cmd.none )

Damit fordert sie die Leistungsgrenzen ihres mobilen Browsers nun doch arg heraus. Und da ein Telefon mit Strom heute den Unterschied zwischen Leben und Tod bedeuten könnte, steckt sie es wieder weg… wo bleiben die anderen bloss?

Sind sie wohl schon aufgebrochen? Oder schon zusammengebrochen? Schaffen Sie den ersten Aufstieg des Elm-Höhenweges? Trink noch einen Kaffe Luz und erfahre im nächsten Teil, wie es React und Angular in der Zwischenzeit ergangen ist.

ManuScripts: Wenn jemand eine Reise tut… Funktionale Programmierung mit Elm – Teil 5 – Noch eine Dosis TypeScript

04. Juli 2016
Manuel Baumann
3
Elm, Javascript, ReactJS, TypeScript
Eine Blog-Serie zum Vergleich von Elm, ReactJS und AngularJS anhand eines praktischen Beispiels.Hier geht es direkt zu [Teil 1, 2, 3, 4, 6]

Es ist schon eine schräge Truppe, wie sie sich da präsentiert beim Ausstieg aus dem Bus vor den Elmer Sportbahnen. Allen voran die erwartungsvolle und leichtfüssige Elm und zuhinderst der schwer beladene Angular, leise vor sich hin fluchend, jedoch genau so tapfer und entschlossen, wie man das bei den anderen spürt.

Es ist ja auch nicht abzustreiten, dass Elm einen klaren Vorteil hat durch ihre angeborenen Fähigkeiten zur statischen Typisierung. Damit die anderen sich in den unwegigen Alpen genau so sicher fühlen können wie Elm, holen beide nun ihre Packungen mit der Aufschrift “TypeScript” hervor, welche sie glücklicherweise in der Apotheke in Glarus noch erstehen konnten.

Während Elm sich in Elm noch etwas umsieht, untersuchen die anderen die beiliegende Packungsbeilage lange und gründlich:

TypeScript für ReactJS und AngularJS

In diesem Teil liegt der Fokus bei TypeScript, welches wir im Zusammenhang mit den beiden Javascript-Werkzeugen Angular und React verwenden. Das Thema wurde bereits angeschnitten in Teil 3.

TypeScript ist Microsofts Antwort auf die von der Entwickler-Gemeinde geforderten Typsicherheit und lehnt im Syntax stark an C# an, da die Sprache offenbar vorwiegend für C#-Entwickler entworfen wurde, damit sich diese schnell damit vertraut fühlen können.

TypeScript wird bereits von vielen populären Editoren unterstützt, vornehmlich von Visual Studio 2015, aber auch viele Open-Source Editoren, wie Sublime Text, Atom oder Eclipse, bieten eine zum Teil recht breite Integration an.

Weil wir sowohl für Angular, als auch für React TypeScript einsetzen, sparen wir hier etwas Zeit, indem wir den Code für die Domäne (vom Englischen: problem domain) und sogar eventuelle Viewmodels teilen können.

Es sei hier nochmals erwähnt, dass aus dem Hause Facebook ein konkurrierender Typ-Checker namens Flow  stammt (https://flowtype.org/), der aus obigem Grund nicht weiter untersucht wird.

Damit wir etwas mehr von TypeScript sehen, werden wir nicht nur den Spielstein entwerfen, sondern gleich den ganzen Sack der möglichen Steine und diesen auf dem Bildschirm darstellen.

Im folgenden werden wir also Code schreiben, der sowohl von ang2-anagram
und react-anagram verwendet werden soll (siehe Teil 2, resp. Teil 3).

Für den Moment legen wir dazu einfach ein weiteres Verzeichnis namens ts-shared an (neben den obigen zwei), wo die in diesem Beitrag erarbeiteten Dateien liegen werden.

Sugar

Zuerst erstellen wir die Datei TileDef.ts:

class TileDef {
  constructor(public letter: string, public count: number, public value: number) { }
}

Der Klassen-Syntax von TypeScript lehnt sich stark an denjenigen von ES2016 an. So liest sich dies nun wie eine typische ES2016-Klasse, da es aber effektiv ein TypeScript-Konstrukt ist, konnte auch die Konstruktor-Parameter syntaktisch erweitert (syntactic sugar) werden, um die Bedienung zu vereinfachen (zu versüssen).

Ohne Zucker sähe die Klasse so aus:

class TileDef {
  letter: string;
  count: number;
  value: number;

  constructor( letter: string, count: number, value: number) { 
    this.letter = letter;
    this.count = count;
    this.value = value;
  }
}

Meiner Meinung nach ist die zweite Version sehr viel verständlicher als die erste, die Verwendung des Zuckers ist hier aber durchaus abzuwägen. Genau wie im privaten Alltag.

Der TypeScript-Compiler wird folgendermassen installiert und verwendet:

  • Installation: npm install -g typescript
  • Verwendung: tsc KompilierMich.ts (erzeugt KompilierMich.js und KompilierMich.map)
  • Konfiguration: tsconfig.json (steuert den tsc-Aufruf.)

Die Existenz von tsconfig.json im TypeScript-Projekt-Root-Verzeichnis führt dazu, dass die Kompilation aller darunterliegenden Dateien mit tsc gestartet werden kann und der Compiler sich gemäss den Angaben in diesem File verhält. Der momentane Inhalt wird gleich aufgeführt, für die Interpretation der Compiler-Optionen, sowie weitere Optionen sei auf die Dokumentation verwiesen.

{
    "compilerOptions": {
        "module": "commonjs",
        "noImplicitAny": true,
        "sourceMap": true,
        "outDir" : "./built"
    },
    "exclude": [
        "node_modules"
    ]
}

(Mit der Angabe outDir können die während der Kompiliererung erzeugten Artifakte in ein eigenes Verzeichnis geschoben werden, und müssen nicht zwischen den .ts-Dateien zu liegen kommen (siehe auch Elm’s Empörung in Teil 2))

Also, tsconfig.json mit obigem Inhalt anlegen, dann tsc starten, und sobald alles kompiliert, geht die Reise weiter.

Rocks

Nebst der TileDef.ts brauchen wir auch noch die Klasse für den einzelnen Spielstein:

class Tile {
  blankoLetter: String;

  constructor(public tileDef : TileDef) {
    this.tileDef = tileDef;
    this.blankoLetter = null;
  }

  setBlankoLetter( letter: String ) : void{
    if( this.tileDef.letter != BLANK ) {
      throw new Error("not a blanko");
    }
    if( this.blankoLetter != null ) {
      throw new Error("blanko already set");
    }
    this.blankoLetter = letter;
  }
}

Für Blankos gibt es mit setBlankoLetter die Möglichkeit, zu einem späteren Zeitpunkt festzulegen, für welchen Buchstaben der Blanko-Stein stehen soll.

Wie schon erwähnt in Teil 3, enthält der Spielsack in der deutschen Variante des SCRABBLE-Spiels zu Beginn 102 Spielsteine. Diese Steine sollen nun erstellt werden und auf den Bildschirm ausgegeben werden.

Baggies

Dazu brauchen wir ein ähnliches Konstrukt wie für die Steine, nämlich eine Klasse namens BagDef für die Definition des Inhalts und eine Klasse namens Bag, welche dann die Spielsteine selbst beherbergt.

Da wir diese Klassen nun lieber in einem anderen File führen würden, müssen wir uns zu diesem Zeitpunkt mit Modul-Exporten und -Importen in TypeScript beschäftigen. (Beachte: “module” : [“commonjs”](http://stackoverflow.com/questions/16521471/relation-between-commonjs-amd-and-requirejs) im tsconfig.json)

Das neue File BagDef.ts sollte eigentlich mit dieser Zeile beginnen, die auf Deutsch sagt, “importiere mir die Klassen TileDef und Tile aus dem relativ gelegenen File ./TileDef.ts”:

import { TileDef, Tile } from “./TileDef.ts”;

Damit das aber so kompiliert, müssen wir das File TileDef.ts noch entsprechend anpassen und die Exporte deklarieren (export vor die Klassen-Definitionen stellen):

export class TileDef { …
export class Tile { …

Es können auch andere Werte exportiert werden:

export const BLANK = ” “;

Nun können wir den Rest der Klassen-Gerüste entsprechend bereitstellen (BagDef.ts):

import { TileDef, Tile } from "./TileDef.ts"

export class BagDef {
  language: String;
  tileDefs: TileDef [];

  constructor( language: String, tileDefs: TileDef[] ) { 
    this.language = language;
    this.tileDefs = tileDefs;
  };

  createTiles() : Array<Tile> {
    return [];     
  }
}

export class Bag {
  bagDef: BagDef;
  tiles: Tile [];

  constructor( bagDef: BagDef ) { 
    this.bagDef = bagDef;
    this.tiles = bagDef.createTiles();
  }
}

export const GERMAN_BAG = 
  new BagDef("german"
            ,  [ new TileDef( BLANK, 2, 0 )
               , new TileDef( "E", 15, 1 )
               , new TileDef( "N", 9, 1 )
               , new TileDef( "S", 7, 1 )
               , new TileDef( "R", 6, 1 )
               , new TileDef( "U", 6, 1 )
               , new TileDef( "A", 5, 1 )
               , new TileDef( "D", 4, 1 )
               , new TileDef( "H", 4, 2 )
               , etc...
               ] 
            );

Es fehlt eigentlich nur noch die Implementation von createTiles. Dazu werden wir mit Arrays von TileDefs  und Tiles  umgehen müssen. Um Array-Operationen zu vereinfachen, so wie sie hier benötigt werden, weiche ich im Normalfall auf die exzellente lodash-Bibliothek aus. (An dieser Stelle muss ich auf das neuere und noch raffinierter ramda.js hinweisen.)

Dopings, ehmm, typings

Um bestehende Bibliotheken mit TypeScript verwenden zu können, gibt es den Typescript Definition Manager oder kurz typings. Wie der Name schon sagt, werden damit TypeScript-Typ-Definitionen verwaltet, die der TypeScript-Compiler verwendet, um die extrenen Typen zu überprüfen.

Wenn wir nun also lodash in unser Projekt bringen wollen, verwenden wir das Tool folgendermassen:

npm init -f --> (legt package.json an)
npm install lodash --save --> (speichert die benötigten Paket-Dateien im Ordner node_modules/ und speichert die Paket-Referenz in package.json) 
npm install -g typings (installiert typings global, benötigt sudo unter Linux)
typings install lodash --save (speichert die lodash-Typdefinitionen im Ordner typings/ und passt typings.json entsprechend an)

Danach kann die Abhängigkeit in BagDef.ts (oder wo auch immer) importiert werden mit:

import * as _ from ‘lodash’;

(importiere alle Exporte aus dem Paket lodash unter dem Alias-Namen _)

Stoned

Die Funktion createTiles soll durch alle TileDefs gehen und so viele Steine davon erzeugen, wie die Definition es vorgibt. Mit lodash kann eine derartige map und concat-Operation mittels flatMap abgekürzt werden:

  createTiles() : Array<Tile> {
    return _.flatMap( this.tileDefs, td => td.createTiles() );  
  }

Da mir meine spontane Entscheidung der Delegation an TileDef gefällt, füge ich die Implementation der neuen Funktion dort hinzu (TileDef.ts):

  createTiles() : Array<Tile> {
    return _.times( this.count, _ => new Tile(this) );
  }

Anmerkung 1: Der verwendete Syntax nutzt die seit ES2015 verfügbaren Arrow-Funktionen. Also x => f(x) anstatt function(x) { return f(x); }, was viel Schreibarbeit erspart und die Leserlichkeit erhöht. (Arrow-Funktionen sind immer anonym und verändern nicht den Kontext von this, im Gegensatz zu herkömmlichen Funktionen.)

Anmerkung 2: Es ist eine allgemein akzeptierte Konvention, ignorierte Parameter mit _ zu bezeichnen. Damit ist klar, es wird zwar ein Parameter mitgegeben, aber nicht verwendet. In diesem Beispiel ist es der Zähler, der von der Funktion _.times an die anonyme Funktion übergeben wird.

Reacts Reaktionen

Die Verwendung im React.JS-Projekt (react-anagram), läuft so ab:

app.tsx (Applikation):

import * as React from "react";
import * as ReactDOM from "react-dom";

import { Bag, GERMAN_BAG } from "../../ts-shared/BagDef";
import { BagView } from "./Components.tsx"

var bagView = <BagView bag={new Bag(GERMAN_BAG)} />

ReactDOM.render(bagView, document.getElementById("main"));
</pre>

Components.tsx (Views):

import * as React from "react"
import { TileDef, Tile } from "../../ts-shared/TileDef";
import { Bag } from "../../ts-shared/BagDef";

interface TileProps {
  tile: Tile;
}

export class TileView extends React.Component<TileProps, {}> {
  render() {
    const {tile} = this.props;
    return <div>Tile: {tile.tileDef.letter}, V: {tile.tileDef.value}</div>;
  }
}

interface BagProps {
  bag: Bag;
}

export class BagView extends React.Component<BagProps, {}> {
  render() {
    return ( 
      <div>
        <div>Bag: {this.props.bag.bagDef.language}</div>
        <div>Letters: {this.props.bag.tiles.length}</div>
        <div>
          <ul>
            { this.props.bag.tiles.map((tile, idx) => 
                <TileView tile={tile} key={idx} />) }
          </ul>
        </div>
      </div> 
     );
  }
}

Die neue Klasse BagView erhält einen Bag als Zustand und stellt mit dem jsx-Syntax den Inhalt des Sacks dar. Mit dem Interface BagProps wird sichergestellt, dass die an die Komponenten übergebenen Daten einer gewissen Form folgen. Die Dokumentation für Interfaces geht auf das verwandte Konzept “Duck-Typing” ein

Fazit

TypeScript ist eine grosse Hilfe und für professionelle Entwicklung in meinen Meinung nach unumgänglig, falls mit javascript programmiert werden muss und das Projekt über 100 Zeilen lang wird. Beide, React und Angular 2, setzen auf TypeScript, da mit der Typsicherheit auch viel syntaktischer Sugar (z.B. Dekoratoren für Angular2-Komponenten) und besserer Tool-Support kommt.

Die Typdeklarationen für die meisten gängigen Bibliotheken wurden schon erstellt, und eigene Deklarationen sind relativ leicht nachzureichen.

Leider kommt mit Typescript auch wieder zusätzlicher Konfigurations- und Wartungsaufwand, der einfach nicht unterschätzt werden darf.

Die Integration mit ReactJS hat sich als relativ einfach erwiesen. Dasselbe für Angular2 zu tun, stellte sich als eindeutig schwieriger heraus, so dass ich die Entwicklung der Angular2-Komponenten lieber dem Leser als “Übung” überlasse. (Code-Kontributionen sind sehr willkommen!)

React ist bestens gelaunt und mit Elm ins Gespräch vertieft, so dass keiner bemerkt, dass Angular seit Beginn der Gondel-Fahrt zusehends bleicher wurde. Das leise Fluchen ist zu einem noch leiseren Jammern geworden, welches jeweils ab dem ersten Ruckeln über die Pfosten-Führung anschwillt und beim anschliessenden Schwenker seinen Höhepunkt mit abruptem Ende findet.

React scheint die Rezeptur besser zu bekommen als seinem Wanderpartner. Ganz im Gegenteil: in einem Moment der Stille zwischen zwei Pfosten, ertappt sich React dabei, wie er sich dank seinen neuen Fähigkeiten nun richtig auf die bevorstehende Wanderung freut. Sein Blick gleitet über das saftige Grün der Stalden-Alp. Er kann seine Gedanken aber nicht lange schweifen lassen, bevor ihn Angular’s Klagen wieder in die Realität zurückholt. Ob Angular die Reise wohl bis zum Ende durchstehen wird?

1234

Tag Cloud

.NET android Angular AngularJs app Arduino ASP.Net automated testing Azure Big Data C# C++ Cloud continuous integration Elm Embedded gRPC Internet of Things IoT Java Javascript M2M OWASP Projektmanagement protobuf Python Raspberry Pi Reactive Programming REST Scrum Security Softwarequalität SPA Testen testing Testmanagement Teststrategie Visual Studio WebAPI windows WPF Xamarin Xamarin.Android Xamarin.Forms Xamarin.iOS

Archive

Current Posts

  • Virtual pty/tty uart listener: Pitfalls on linux socat
  • Akzente setzen mit der Android Splash Screen API unter .NET MAUI
  • Do You have Your Personal Space?
  • Automated provisioning with ARM Templates
  • Asynchrone Beobachtungen und Versprechungen in Angular

Last Comments

  • Hans Reinsch bei Der Safety-Plan: Die wichtigsten Antworten mit Checkliste
  • George H. Barbehenn bei Modeling Optocouplers with Spice
  • Noser Blog Touch-Actions in Xamarin.Forms - Noser Blog bei Mach mehr aus Animationen in Xamarin.Forms mit SkiaSharp
  • Noser Blog Focus on the Secure Storage service of Trusted Firmware (TFM) - Noser Blog bei First run of the Trusted Firmware (TFM) application
  • Noser Blog First run of the Trusted Firmware (TFM) application - Noser Blog bei Focus on the Secure Storage service of Trusted Firmware (TFM)

Popular Posts

Xamarin.Android Code Obfuscation

6 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 1 - Aufbruch

5 Comments

ManuScripts: Wenn jemand eine Reise tut... Funktionale Programmierung mit Elm - Teil 2 - Kein Picknick

4 Comments

Contact us

  1. Name *
    * Please enter your name
  2. Email *
    * Please enter a valid email address
  3. Message *
    * Please enter message
© 2013 NOSER ENGINEERING AG. All rights reserved. Datenschutz | Cookie-Richtlinie