EinfĂĽhrung in Apples neue Programmiersprache Swift, Teil 1

Seite 3: Funktionen

Inhaltsverzeichnis

Unter Funktionen versteht Swift globale sowie lokale Funktionen und Methoden, also alles, was mit dem Schlüsselwort func beginnt und bei dem oben Argumente reinkommen und unten ein Ergebnis herauskommt, nachdem die Funktion ihre Arbeit verrichtet hat. Die beliebteste Beispielfunktion ist die Berechnung der Fibonacci-Zahlen, die sich ihren Rang aber mit der Fakultätsfunktion teilen muss:

func fib(n : Int) -> Int {
if n <= 1 return n else
return (fib(n-2) + fib(n-1))
}

Die Angabe des Resultatstyps Int befindet sich unmittelbar nach dem Pfeil, was mathematisch Versierte nicht weiter überraschen dürfte. Liefert eine Funktion keine Ergebnisse zurück, können Entwickler alles, was im Funktionskopf nach der Parameterliste kommt, weglassen, wie in

func alarm() {/* don't panic */} 

An dieser Stelle sollte ein weiterer Unterschied gegenüber vergleichbaren Sprachen Erwähnung finden. In C, C++, C# und Java lässt sich ein bedingter Ausdruck auch wie im Beispiel schreiben:

var x = 0 

// woanders dann verwendet in:

if (x)
System.out.println("nicht null")
else
System.out.println("null");

Das ist in Swift nicht zulässig. Dort müsste man explizit in der Fallunterscheidung

if (x != 0) statt if (x)

schreiben, also einen booleschen Ausdruck.

Zusätzlich führt die Sprache eine weitere Einschränkung ein, um Fehler zu vermeiden. Bei einer Zuweisung var x = <rechte Seite> liefert der Zuweisungsoperator keinen Wert zurück wie in C++. Zuweisungsketten nach dem Schema x = y = z = 4711 existieren deshalb in Swift nicht. Was sich auf den ersten Blick wie Fußfesseln anfühlt, bedeutet andererseits, dass der Compiler Verwechslungen zwischen Gleichheits- und Zuweisungsoperator als Fehler monieren kann. Steht im Quellcode also

if (x = 0) ...

meldet der Compiler einen Fehler, weil Zuweisungen kein Resultat erzeugen. In C oder C++ lässt der Compiler das anstandslos durchgehen. Aber nun zurück zu den Funktionen.

Funktionen können auch selbst wieder interne (Hilfs-)Funktionen definieren, woraus Funktionshierarchien und -verschachtelungen resultieren. Da sie sich nur unwesentlich von denen in Objective-C, C#, Java und Scala unterscheiden, beschränkt sich der Artikel auf Swift-spezifische Aspekte. Das gilt auch für Anweisungskonstrukte wie Schleifen, Selektionen und Abfragen.

In der Funktionssignatur kann das SchlĂĽsselwort var vor einem Parameter stehen:

func  zeichne(var n : String)  {
n = n + 1
...
}

Mit diesem Parameter ist nicht etwa ein "call by reference" gemeint. Der Compiler legt stattdessen für die Funktion lokal eine Variable n an, die mit dem Wert des übergebenen Parameters initialisiert wird und die der Funktionsrumpf anschließend nutzen darf. Wer hingegen das Originalobjekt in einer Funktion ändern möchte, stellt bei der Parameterdeklaration statt var das Schlüsselwort inout voran.

func  orakeln(frage : String, inout antwort : String)  {
antwort = antwort +" 42"
}

Beim Aufruf müssen Klienten dieser Funktion das aktuelle inout-Argument mit &-Operator übergeben – C++ lässt grüßen.

var antwort = ""
orakeln(&antwort)

Funktionen erlauben die Implementierung von Operatoren für eigene Datentypen. Die seltsam anmutenden Zusätze wie @prefix im unteren Beispiel stellen Annotationen, das heißt Metadaten dar, die der Compiler oder ein anderes Werkzeug verarbeiten kann. Zum Beispiel bedeutet die Annotation @prefix, dass der implementierte Operator in Präfix-Position angewandt wird.

@prefix @assignment func +++ (inout vector: Vector2D) -> Vector2D {
vector += vector
return vector
}

Das Definieren eigener Operatoren beschränkt sich nicht auf die Standardoperatoren. Jedes Zeichen lässt sich als Operator verwenden.

Variable Parameterlisten (Variadic Parameters) erlauben, wenig überraschend, eine variable Zahl von Aufrufargumenten. Das benötigen Entwickler etwa für Funktionen wie:

func summe(summanden: Double...) -> Double { 
var ergebnis: Double = 0
for summand in summanden { // iteriere ĂĽber Liste
ergebnis += summand
}
return ergebnis
}

Müsste man das ohne variable Parameterlisten implementieren, ergibt sich daraus unnötiger Ballast. Die Funktionssignatur des Beispiels spezifiziert eine variable Liste von Summanden als Parameter. Der Funktionskörper iteriert über die Liste variabler Parameter und summiert jede Zahl zur Variablen ergebnis. Ein dazugehöriger Aufruf hätte etwa folgende Form: summe(1.0, 2.0, 3.0, 4.5).

Funktionsparameter mit vordefinierten Werten (Default) sind der nächste Baustein. Soll heißen, es gibt einen Defaultwert für den Parameter, den die Funktion nutzen kann, falls der Aufrufer keinen entsprechenden Parameter zur Verfügung stellt. Vorinitialisierte Parameter müssen immer am Ende der Parameterliste deklariert sein.

func maleRose(farbe : String = "Rot") { 
/* geheim " */
}

Gibt sich der Aufrufer mit Rot zufrieden, ruft er einfach maleRose() auf. Falls nicht, spezifiziert er die gewĂĽnschte Farbe als Argument: maleRose("Blau-Weiss").

Wie erwähnt, sind Funktionen Bürger erster Klasse, insofern als die Swift-Schöpfer sie als Datentypen betrachten, die auch als Parameter oder Resultat bei Funktionsaufrufen oder als rechte Seite in Variablenzuweisungen auftreten können. Also überall dort, wo auch sonst konventionelle Datentypen auftauchen. Zur Veranschaulichung soll wieder ein einfaches Beispiel dienen, bei dem eine Funktionsvariable einfacheFunktion mit der Funktion nachfolger initialisiert und dann als Argument an die kommando-Funktion übergeben wird:

func nachfolger (x : Int) -> Int {
return x + 1
}

func kommando (f: (Int)->Int, x : Int) -> Int {
return f(x)
}

var einfacheFunktion : (Int) -> Int = nachfolger
println("Result: \(kommando(einfacheFunktion, 2))")

var y = kommando(nachfolger, 41)

Die Funktion kommando erwartet als Parameter eine Funktion und ein ganzzahliges Argument, dessen Anwendung auf nachfolger() und 41 das Ergebnis 42 in der Variablen y speichert.