Funktionale Programmierung mit Swift
Letztes Jahr hat Apple mit Swift eine neue Programmiersprache vorgestellt. Über kurz oder lang wird Swift die Standardsprache werden, um Apps für iPhone, iPad, Mac und Co zu entwickeln. Und Swift enthält viele Elemente der funktionalen Programmierung, Zeit genug also, dass wir in diesem Blog mal einen genaueren Blick auf die Sprache werfen.
Interessant ist, dass Apple in Swift nicht nur einfach ein paar liebgewonnene Features aus funktionalen Sprachen eingebaut hat. Nein, es scheint vielmehr, dass grundlegende funktionale Designparadigmen wie „Werte statt veränderbare Objekte“ und „Seiteneffekte ja, aber mit Disziplin“ auch in Apples Vorstellung von guter Softwarearchitektur eine große Rolle spielen. Exemplarisch seien hier zwei Vorträge der Apple Worldwide Developers Conference genannt. Im Vortrag Building Better Apps with Value Types in Swift geht es um die Vorteile von Werttypen (value types) in Swift, also von Typen deren Werte unveränderbar sind. Und der Vortrag Advanced iOS Application Architecture and Patterns schlägt in dieselbe Kerbe, hier geht es ebenfalls um Werttypen und Unveränderbarkeit (immutability).
Im heutigen Blogartikel schauen wir uns anhand eines Beispiels Swift etwas genauer an. Wir möchten eine kleine Bibliothek zum Zeichnen von Diagrammen designen und implementieren. Damit können wir dann z.B. solche Diagramme zeichnen:
Das geht natürlich auch mit herkömmlichen, imperativen Mitteln. Etwa so:
Dieser Code benutzt die Mac API zum Zeichnen, aber das Grundprinzip ist in
fast allen UI-Toolkits gleich: wir benutzen einen Grafikkontext ctx
, um
primitive Formen wie Rechtecke und Kreise auf den Bildschirm zu
zeichnen. Wir sagen dem System also genau, wie gezeichnet werden soll.
Was passiert nun aber, wenn wir das Diagramm leicht ändern möchten und z.B. eine grünen Kreis zwischen die beiden Rechtecke einfügen wollen?
Mit dem imperativen Ansatz (wie wird gezeichnet) müssen wir nicht nur neuen Code für den Kreis schreiben, sondern wir müssen auch bestehen Code ändern, um das rote Rechteck weiter nach rechts zu schieben:
Mit einem funktionalen Design werden solche Probleme vermieden. Denn funktional gedacht spezifizieren wir lediglich was gezeichnet werden soll und überlassen das wie einer Bibliothek.
Im Folgenden schauen wir uns an, wie wir in Swift Diagramme deklarativ spezifizieren können und wie wir eine Bibliothek zum Umsetzen der Spezifikation in echte Bilder realisieren können. Die Idee zu diesem Beispiel stammt aus dem schönen Buch Functional Programming in Swift von Chris Eidhof, Florian Kugler und Wouter Swierstra, die Ideen sind aber auch z.B. schon in der Haskell Bibliothek diagrams zu finden.
Spezifikation von Diagramm
Um zu spezifizieren, was in einem Diagram enthalten sein soll, benutzen
wir das enum
-Feature von Swift. Wir beginnen mit einfachen geometrischen Formen:
Die Enums können aber mehr als einfach nur verschiedene Fälle zu einem
Typen zusammenzufassen. Wir können z.B. auch Werte mit einzelnen Fällen
assoziieren. Exemplarisch hierfür definieren wir das Enum Attribut
, welches wir
später verwenden, um Diagramme einzufärben und um die Anordnung zu spezifizieren.
Es geht aber noch mehr! Enums können auch rekursiv sein, d.h. wir können
innerhalb der Definition eines Enums das Enum selbst verwenden. Dazu
brauchen wir das Schlüsselwort indirect
.
Ein Diagramm ist also entweder eine primitive Form mit einer Größe (die
Größe ist nicht in Pixel angegeben, sondern relativ zu den anderen
Diagrammelementen gedacht), oder zwei Diagramme neben- bzw. untereinander,
oder ein annotiertes Diagramm. Für solche annotierten Diagramme benutzen
wir das bereits definierte Enum Attribute
.
Enums in Swift sind also viel mächtiger als reine Aufzählungstypen wie beispielsweise in Java oder C#. Das, was Enums in Swift sind, ist in funktionalen Sprache Standard; sie heißen dort algebraische Datentypen oder auch Summentypen.
Beispiel-Diagramme
Nun schauen wir uns an, wie wir mit diesen Enums ein Diagramm spezifizieren können:
Mit let
führen wir eine neue Variable blueSquare
ein, deren Wert nicht
verändert werden kann. Diagram.Primitive
erzeugt ein neues primitives
Diagramm, dem wir dann mit Diagram.Annotated
eine Farbe verpassen.
Das obige Diagram blueSquare
ist sehr einfach und besteht nur aus einem blauen
Quadrat. Trotzdem ist der Code zum Erzeugen etwas länglich. Wir können ihn
vereinfachen, indem wir Hilfsfunktionen bereitstellen. In OO-Sprachen sagt
man zu solchen Funtionen oft „smarte Konstruktoren“, in funktionalen
Sprache werden sie auch „Kombinatoren“ genannt. Wir starten mit smarten
Konstruktoren für einfache Formen.
Nachfolgend definieren wir auch smarte Konstruktoren für Farbe und Alignment.
Wir haben diese Funktionen als Erweiterung (extension
)
von Diagram
geschrieben, damit wir die „Dot-Notation“ verwenden können,
um die Funktionen wie Methoden auf einem Diagram aufzurufen. Innerhalb einer solchen
extension
verwenden wir wie immer self
, um auf das Diagram zuzugreifen, auf dem
die Methode aufgerufen wurde.
Wir sehen die Dot-Notation gut an folgendem Beispiel:
Wir konstruieren zuerst ein Quadrat, um dann auf dem resultierenden
Diagramm fill
aufzurufen. Wenn wir fill
als globale Funktion
geschrieben hätten, müssten wir stattdessen so etwas schreiben:
fill(NSColor.redColor(), square(2))
. Welche der beiden Schreibweisen wir
wählen ist Geschmacksache, ich habe mich für die Dot-Notation entschieden,
weil sie meiner Ansicht nach zu leichter lesbarem Code führt.
Es fehlen noch smarte Konstruktoren zur Platzierung von Diagrammen
nebeneinander- bzw. untereinander. Diese realisieren wir als
Operatoren. (Auch diese Entscheidung ist Geschmacksache, wir hätten
genauso gut normale Funktionen verwenden können.) Die Operatoren sind
dabei |||
für nebeneinander und ---
für untereinander.
Durch die associativity
Notation lassen wir den Swift-Compiler wissen,
dass er einen Ausdruck ohne Klammern wie z.B.
blueSquare ||| redSquare ||| diag1
als
(blueSquare ||| redSquare) ||| diag1
verstehen soll.
Jetzt können wir noch ein paar mehr Diagramme definieren:
Die Diagramme sampleDiagram1a
und sampleDiagram1b
haben wir bereits weiter
oben als Bilder gesehen.
Das letzte Diagram sampleDiagram2
demonstriert die Verwendung von
---
. Wir benötigen alignBottom
und alignTop
, damit der obere Teile
des Bilds sampleDiagram1b
und das langgezogene, magentafarbene Rechteck
direkt übereinander liegen. So sieht sampleDiagram2
dann aus:
Bis jetzt haben wir gesehen, wie man mittels Enums Diagramme einfach und kompakt repräsentieren kann. Wie man diese Diagramme dann auf den Bildschirm zeichnet, das werden wir in einem Folgeartikel diskutieren. Bis dahin freue ich mich über Rückfragen und anderes Feedback. Viel Erfolg beim funktionalen Programmieren in Swift!
Übrigens: wir arbeiten gerade daran, den Objective-C Code für unser Produkt Checkpad MED zumindest teilweise auf Swift zu aktualisieren. Falls Sie Lust und Interesse haben, daran mitzuhelfen, dann freuen wir uns auf Ihre Bewerbung.