Apple Swift: A Swift Tour
So, da bin ich wieder, wie versprochen hier jetzt der erste Teil meiner Analyse von Swift. Ich sage hier nochmal ausdrücklich, das hier bezieht sich auf das erste Kapitel “A Swift Tour", ergo werden hier evtl. Dinge gesagt, die in späteren Kapiteln verbessert oder geändert werden.
Gut, fangen wir mal an.
Script?
Was mir ja so gar nicht liegt, ist diese Script-Mechanik, wie sie viele funktionale Sprachen haben, so ganz ohne klaren Einstiegspunkt, etc.. Was ist, wenn ich mehr als eine Sourcedatei habe, wo startet mein Programm dann? Ist einfach nicht so meine Welt, aber vllt. bin ich durch Java und C# etwas verhunzt was das angeht. Ich sollte mir mal mehr funktionale Sprachen ansehen, vllt. werde ich dann damit mal warm. Blicke ja schon seit ewig und drei Tagen auf Dylan, aber den Compiler habe ich bisher nicht ans Laufen gebracht. Aber ich schweife ab, hier soll es um Swift gehen, nicht um Dylan (Kommt vllt. irgendwann mal).
println()
Okay, eine einfache Ausgabefunktion, schön und gut. Immer da, ohne weiteren Import, okay kann man machen. Finde aber, das knüpft Swift-Programme stark an die Shell. Bei C (wobei es hier auch nicht ohne Import geht) ist das okee, da es ja eine allg. Sprache ist, die relativ nativ läuft und was ist nativer als eine Shell? Swift hingegen ist ja eig. dazu gedacht, ObjC abzulösen und somit primär für Desktop-Applikationen von OSX und iOS da. Hier werden dann natürlich Ausgaben auf der Shell zweitrangig. Dies gilt gerade für iOS, wo man keine lesbare Shell hat, ohne das Gerät an einen herkömmlichen Computer anzuschliessen. Deswegen finde ich es echt befremdlich, eine so einfache Shell-Ausgabefunktion, die allgegenwärtig ist, zur Verfügung zu stellen. Es vereinfacht nur die Einstiegshürde, weil hey, schau mal, ich mache ein HelloWorld! in einer Zeile, ich kann Swift programmieren! Ich finde es zwar nützlich für Einsteiger, aber irgendwie finde ich es auch komisch.
String Escape
Wer hatte es nicht schonmal. Zb. Java:
int propertyA = 3;
int propertyB = 5
void String toString() {
return "Ein String" + propertyA + propertyB;
}
Was genau bekommen wir? "Ein String 35" oder "Ein String 8"? Muss ich es klammern? Oder nicht? Macht es einen Unterschied, Leerzeichen zwischen Variablennamen und Operator zu lassen? Ist der + Operator definiert mit impliziter Konvertierung (Object->String) mit dem String als 1. oder 2. Summand? Ich kann es mir nicht merken und probiere jedesmal aufs Neue aus. Ich finde die Idee gut, ein String Escape zu haben, in dem man rechnen kann.
let propertyA : Int = 3
let propertyB : Int = 5
func toString() -> String {
return "Ein String \(propertyA)\(propertyB)"
return "Ein String \(propertyA + propertyB)"
}
Dadurch hat man eine definierte Vorgehensweise, und all diese Verwirrung hört auf. Bitte Java, tu was dagegen. Und nein, String.format ist keine Lösung dafür. Ich mag String.format zwar gerne und habe mir angewöhnt, es zu benutzen, aber für so kleine Sachen ist es doch etwas overhead, finde ich.
Listen und Maps
Einfach mal spontan Listen erzeugen mit [item1, item2, item3] finde ich richtig gut. Wie oft braucht man doch eine Liste mit einem einzigen definierten Element, da die Funktion, die man benutzen will, nur mit Listen arbeitet. Und das auch noch mit maps zu machen. Nettes I-Tüpfelchen. Diese mit dem [index] accessor zu haben, finde ich auch viel besser, als Javas Krankheit mit den get-Methoden. Es fühlt sich halt natürlicher an, da man es ja von den Arrays so kennt.
foreach
So, kommen wir zu for-Schleifen. Zur klassischen Zählschleife habe ich nicht viel zu sagen, die ist ja fast überall gleich. Mir geht es gerade um die foreach. Ich finde weder C# noch Java machen es konsequent. Java benutzt Schlüsselwörter (extends/implements) für die Ableitungen, aber den ":” Operator, um Listen anzusteuern. C# macht es andersrum, und benutzt den ":” Operator für Ableitungen (Ist ja immer noch C/C++) und das "in" Schlüsselwort für Listen. Ich persönlich bin ein Fan von Schlüsselwörtern. Warum nicht extends und implements und for(x in list)? Swift verhält sich hier btw. wie C#, aber naja ist halt auch C/C++. Bin ja froh, dass niemand den Type::Method operator benutzt um zu definieren, für welche Ableitung man jetzt Dinge tut...**hust* Java8 hust.*
Type? -> Nullable
Okay, explizit definieren zu müssen, ob etwas null oder nil sein kann, kann man drüber streiten. Klar, wenn man Java programmiert, muss man eben darauf gefasst sein, dass alles, was man bekommt, evtl. null sein kann. Ich finde, es schadet nicht, daran aber auch erinnert zu werden durch ein ? am Datentyp.
Die Prüfung auf nil mit* if let x = nullableVar* zu machen, finde ich nicht so gut gelöst, es ist unleserlich meiner Meinung nach. Mich stören schon so Konstrukte wie while(int buffersize = readBuffer() != 0){ //doStuff }. Ist recht abartig und unverständlich. Klar, die wollten keine implizite Typ-Konvertierung mehr haben. Und diese int gegen 0 Testungen. Aber könnte man bei Nullables nicht die Ausnahme machen, dass diese den Wahrheitswert haben, ob sie nil sind oder nicht? À la
let x : Int? = nil
if(x) {
//x is not nil
} else {
//x is nil
}
Wäre jetzt mal so nen Vorschlag.
switch case
Klar, das Durchfallen von cases ist praktisch, und echt ein nettes Konstrukt. Aber mal im Ernst, wie häufig benutzt man switches mit durchfallenden cases im Vergleich zu denen ohne? Macht es da dann nicht mehr Sinn, das break am Ende jedes cases wegzulassen, und multiple cases zu machen. Und voilà, Swift macht es. Gut, aber dann bei enums zu meckern, dass nicht alle Möglichkeiten mit cases abgedeckt sind, wenn man kein default case hat. Ob das so sein muss? Aber grundlegend ist der Ansatz sehr gut. Und dass man richtige Bedinungen machen kann, wie case let x where x.propertyA = 5 finde ich auch gut, da es übersichtlicher ist, 2 nahezu gleiche cases zu haben, wo nur einzelne Details anders sind, als ein großes case mit if-Anweisungen drin. Dass hier jetzt zwingend dieses let mit drin sein muss, finde ich ähnlich unsinnig, wie bei den if let Anweisungen. Warum muss ich für den neuen Kontext eine neue konstante Variable anlegen mit dem Inhalt der alten? I don’t get it, sorry.
foreach auf maps
Ohh endlich, wie bescheuert war das bisher in anderen Sprachen. Erst über die keys iterieren, und dann damit die values ansteuern, oder über KeyValuePair Tuple iterieren und sich da dann die Daten herrausziehen. Endlich einfach mal einen Identifier für den key, und einen für das value angeben, und los.
for range
Die Möglichkeit, eine Zählschleife mal eben wie eine foreach schnell zu schreiben, ist ja ganz nett. Aber wie gibt man die Range an. 1..3 ist das jetzt 1 bis 3 inklusive oder exklusive der 3? Swift löst es eben mit min..max+1 oder min...max. Oder wie sie es nun haben min..<max+1 und min...max. Ich finde es immernoch suboptimal. Warum überhaupt beides? Und warum nicht mit einem selbstsagenden operator. Wie min..<max+1 oder min..=max. Idee finde ich super, Umsetzung ist semigut.
funcions
So, endlich mal bei den Funktionen angekommen. In Swift sind Funktionen first class types, was bedeutet, dass Funktionen nicht zwingend an ein Objekt gebunden sind. Sie können daher einfach stateless benutzt werden. Zusätzlich können sie aber auch innerhalb einer Klasse als Methoden benutzt werden. Somit können dort Funktionen state-abhängig bzw. -verändernd sein. Dieses Konzept liegt mir nicht so, aber das liegt wohl auch daran, das ich noch nie richtig damit gearbeitet habe. Das Einleiten mit func ist okee, zwar ungewohnt, aber da es ja first class types sind, ist das wohl berechtigt. Klassen werden ja auch mit class eingeleitet. Klar könnte man es auch ausschreiben, aber gut. Dass man den Returntype mit func a() -> ReturnType angibt, finde ich irgendwie gut, kann aber nicht genau sagen, warum. Es gefällt einfach. Die Tatsache, dass man Tuple einfach als Rückgabewerte haben kann, ist die nächste Neuerung, die echt klasse ist. Klar kann man das in Java und C# auch machen, aber da gibt es nur Wrapper dafür. Hier funktioniert es out of the Box ohne Zusätze. Dass man wie in vielen anderen Sprachen variable Parameterlisten mit ... am Parametertyp definiert, finde ich inkonsequent gegenüber der range definition der for-Schleife.
Closures
Naja, unbenannte Funktionen, was soll man dazu groß sagen. Die notation
{
(paramA:String, paramB:String) -> String in
return "\(paramA) \(paramB)"
}
finde ich etwas übertrieben. Überall wo man ein Closure verwendet, ist der Rückgabewert bekannt, sowie auch die Parameter Typen. Warum es dann noch explizit beschreiben? Aber man kann es ja abkürzen.
{
(paramA, paramB) in
"\(paramA) \(paramB)"
}
Und wenn es nur eine einzelne Anweisung ist, kann man sich das return auch sparen. Und es geht auch noch kürzer.
{"\($0) \($1)"}
Das finde ich aber doch etwas übertrieben, und auch vollkommen unlesbar.
Das war der erste Teil, der zweite wird von Klassen handeln, aber immer noch das 1. Kapitel thematisieren.