Swift has clearly become Apple's flagship programming language. If you want to program a native iOS or Mac application in 2017, it is basically the only officially supported choice.
But Apple's ambitions with Swift seem to reach beyond their own ecosystem. With the language going open source early on, Linux binaries soon followed. With them came the expected flood of express / sinatra / rails clones of all shapes and sizes. Apple developers might finally be able to program their backends without straying too far from the mothership. Articles like this are already crowing how Swift web services are faster than node.js (in a very contrived CPU-intensive test, but oh well). Chris Lathner, the language author, is clear: the goal of Swift is world domination. Not yet another strange proprietary thing people have to swallow to get their weather apps into the AppStore.
So, I've decided to give Swift a try. And since I have little interest in making exclusive Mac or iOS apps, I will do it on Linux and focus entirely on the server side.
I will start this article as a complete Swift newbie. Other than some tech punditry, I haven't read a single tutorial or book beforehand. I will note my observations and findings as I go along. The goal of this first article will be to master enough of Swift's control structures and data types to be able to write algorithm-centric programs and build them into binaries.
Getting Swift to run
Currently (early 2017), the official Swift site offers binary downloads for Mac and Ubuntu.
Since I am running Debian Testing, I will try my luck with the Ubuntu 16.04 binaries. If you are running a different flavor of Linux, good luck with building from sources, I guess.
wget https://swift.org/builds/swift-3.0.2-release/ubuntu1604/swift-3.0.2-RELEASE/swift-3.0.2-RELEASE-ubuntu16.04.tar.gz
tar xavf swift-3.0.2-RELEASE-ubuntu16.04.tar.gz
Apple seems to expect people to dump the content of this package into their /usr
directory. There's no way I am doing that. Instead, I decided to do a local install under my home directory. This way I can later easily upgrade Swift to a newer version (hopefully the one that comes with my linux distribution).
mkdir ~/swift
mv swift-3.0.2-RELEASE-ubuntu16.04/usr/* ~/swift/
PATH="${HOME}/swift/bin:${PATH}"
I added this last line into my ~/.bashrc
as well, so swift binaries are always available.
At this point, I was able to type swift
in my console and get a working REPL environment. Yay, "hello world"
here I come!
print("Hello world")
It turns out getting the damn thing to build is a bit more complicated. My hello world code went into the main.swift
file, which I saved in the sources
directory (ahh, good old Apple verbosity). Then, in the main directory of my project, I created a Package.swift
file with the following content:
import PackageDescription
let package = Package(
name: "hello"
);
At this point I got some missing dependency errors, which I solved with apt-get install clang
. Your mileage may vary depending on how much dev stuff you already have installed on your box.
Once I got the compiler working, I was able to run swift build
, which produced the executable binary inside the hidden .build/debug
directory.
I suppose there are also release
builds, but I didn't need those so far.
My initial thoughts
Looking at these initial bits of code, here are my thoughts.
-
The language looks rather unremarkable. 201X version of C#. This is good. Pragmatic beats fancy in language design.
-
I've noticed inconsistent semi-colon usage in these snippets of code I've pasted from Internet. Oh no. It's that semi-colon insertion crap all over again. Did they really need to import that particular can of worms from javascript?
-
I presume capitalized
Package
thing is the class constructor. There is no thenew
keyword. Can this lead to confusing constructors vs function calls? -
Package
call accepts what appear to be named parameters. Neat! -
It seems
import
statement dumps all symbols from the imported package into current file's scope. Kind of likeimport *
in python. I hope I am mistaken, I prefer explicit imports.
Time to hit the books
Now that I have basic REPL and compiler working, it's time to actually learn how to write Swift. And maybe answer some of the questions posed above.
After a quick bit of googling, I concluded the best resource to learn Swift is still Apple's official documentation. I've skimmed through the first few chapters of their GuidedTour and almost the entirety of TheBasics.
I found Apple's writing style a bit too babying for experienced programmers coming from other languages (and paradoxically, probably too technical for total beginners). I am hoping this article can serve as TLDR for the people coming from similar backgrounds as me.
var
is used for declaring mutable values, let
for immutable. Types are infered from the assignment, like in C#. Strings and numbers work about how you'd expect.
let a = (5 * 7) - 6
var str: String = "Hello"
Yes, Swift does have semi-colon insertion. I haven't heard too much complaining, though, so they must have done it right.
==
and !=
will test value equality and are expected to be overloaded by classes. ===
and !==
will test reference equality and they only work for actual reference types (eg. 5 === 5
will throw an error)
There is string interpolation:
let word = "World"
print("Hello, \(word)")
This syntax is IMO awful and it appears to have been picked solely to be different from other languages.
nil
is Swift's version of null
or None
. They go into breathless details trying to convince you how this is some amazing advancement. To me, it looks like the same old thing. Just take note of those ?
-s and !
-s, that's how you do the declaration and "unboxing" of nullable variables.
let maybe: Int? = 5
if maybe != nil {
print(maybe!)
}
Variables are tied to scope. No javascript style surprises here.
if true {
let x = "Inside scope"
}
print(x) // error: use of unresolved identifier 'x'
Swift has tuples. You can give members name or access them through indexes. Cool!
let result = (status: 200, message: "OK")
print("Server returned \(result.status) (\(result.1))")
(Enjoying that wonderful string interpolation syntax yet?)
Array and dictionaries have literal syntax similar to PHP (with square brackets). It doesn't seem like you can treat dictionaries as quasi objects (without quoting keys), which means dealing with JSON api-s in Swift will never be as smooth as in javascript.
var array = [Int]()
array.append(5)
var dict = [
"array": array
]
Also like in PHP, arrays and dictionaries are treated as value types. They are copied on assignment (well, copied on first write). let
-declared arrays can't mutate their content. Two arrays can be deep-compared using ==
.
Note that, since arrays are not reference types, you can't compare them using ===
. You just have to trust that ==
will do the right thing and not kill your performance. For more details, see here http://stackoverflow.com/questions/27567736/compare-arrays-in-swift
if
statements look like if
statements.
// Where is your God now, anti-semicolon people!?
if true { print("yes"); } else { print("no"); }
Note that if
statements and comparison operators in general are super stodgy and will only take pure boolean arguments. Swift doesn't seem to have a concept of "falsy" values. This sucks.
for
and while
loops are pretty standard looking. You can iterate collections or ranges.
let nums = [1, 2, 3]
for i in 1...3 { // or 1..<3 to exclude the upper bound
for n in nums {
print(i * n)
}
}
// 1 2 3 4 5 6 7 8 9
Swift has functions and closures. Closures are basically Swift's name for "lambdas". func
syntax is just syntactic sugar around these.
let doubleStrClosure = {
(s: String) -> String in
s + s
};
doubleStrClosure("abc") // abcabc
These things are supposedly first-class values, but not really, seeing how no equality operator works with them. Also, while you can declare private internal functions, there is no hoisting.
But here is the strangest part. All arguments in standard function syntax are by default labeled. If you want positional arguments, you need to prefix them with _
.
func add(_ a: Int, b: Int) -> Int {
return a + b
}
add(1, 2) // Error: missing argument label 'b:' in call
add(1, b: 2) // 3
There are also special cases for class methods and constructors.
Needless to say, compared to the simplicity and power of, for example, Python's calling convention, this sucks. And it is supposed to be like the 4th iteration of the spec! Swift designers are obviously having a lot of problems with this, probably due to the Objective C weird syntax hanging over their heads.
Functions are so far my least favorite part of Swift.
Swift error handling is based on catching and throwing exceptions.
do {
try somethingRisky()
somethingElse()
}
catch (ErrorEnum.errorType) {
//...
}
Separation of do
and try
lets you catch errors from only certain statements within the same block. The utility od this seems dubious.
You can convert a thrown error into an optional
(nullable type), if you don't care about the specifics of the error. That's neat.
let riskyResult = try! somethingRisky();
if riskyResult == nil {
print("There must have been an error, I guess")
}
Program 1: FizzBuzz
This should be enough theory to allow me to write some simple algorithm-based programs.
Let's start with everyone's favorite coding interview question.
Print numbers 1 to 100, except every 3rd number should be replaced with Fizz, every 5th with Buzz and every 15th with FizzBuzz.
for i in 1...100 {
if i % 15 == 0 {
print("FizzBuzz")
}
else if i % 3 == 0 {
print("Fizz")
}
else if i % 5 == 0 {
print("Buzz")
} else {
print(i)
}
}
This was straightforward enough. Procedural style Swift looks like any other C-like language.
Program 2: Reverse words in a string
Write a function that reverses words in a given string
Swift seems to have the most complete Unicode support of all languages. It has separate concepts of the high-level String and low level CharacterView, which is a collection of Character-s.
I thought I would have to mess with this character array, but for this task, the correct solution turned out to be to import Foundation
library. This added the components
method on string (like extension methods in C#
?), allowing me to perform the cutting directly on the String.
import Foundation
func reverseWords(_ str: String) -> String {
return str
.components(separatedBy: " ")
.reversed()
.joined(separator: " ")
}
print(reverseWords("Quick brown fox jumps over a lazy dog"))
Regex will have to wait for some other time.
Program 3: Distinct values
Write a function that takes in an array and returns a new array with only the distinct values
func distinct<T: Hashable>(_ arr: [T]) -> [T] {
var s = Set<T>()
for el in arr {
s.insert(el)
}
return Array(s)
}
print(distinct([1,2,1,5,0,5,3]))
I did a little stretch goal for this one and dipped into Generics. Luckily, they seem to work exactly the same as in C#. Set
is a built in type, nothing strange here, it's just that it doesn't have literal syntax, unlike arrays and dictionaries. Hashable
is type constraint, it probably allows value types to be compared in a generic way.
Next steps
Now that I can write basic ifs and loops, I want to start digging into the IO side of things. I want to learn how to read command line arguments, execute shell commands, mess with files and, eventually, go out to the web. I'll also need to dig into the memory management stuff, class system and a whole lot more.
So far, I'm enjoying my time with Swift. Here's hoping that keeps up.