Advent of Code in Swift
Last month I took part in Advent of Code, an Advent calendar of small daily programming puzzles.
These puzzles can usually be solved in a hundred lines of code or so, and they are easiest in high-productivity languages like Python. I solved the puzzles in Swift, which made for an interesting experience. (OK, confession, the puzzles got more time-consuming as Christmas approached, right when I had less time available, so I haven’t completed them all).
Overall, doing Advent of Code (AoC) in Swift was fun, but there were a few obstacles along the way which made things more awkward than they should be.
As I went through the puzzles I made a note of each “paper cut” I encountered, and I present them below in no particular order! Most of these relate to the Standard Library — the language itself is amazingly productive.
exit()
A very common thing to want to do in short scripty programs is to end the program. In AoC this often happened when my program had found the solution and I wanted to finish. What are the options in Swift?
You can call fatalError()
but this isn’t right — it prints a scary stack trace.
The right solution is exit()
but weirdly this isn’t part of the standard library, you have to import Darwin
to use it. Which means your program doesn’t compile on Linux, so you actually have to do:
#if os(Linux)
import Glibc
#else
import Darwin
#endif// Now I can call exit()
exit(0)
Ugh. Swift should add exit()
(or something like it) to the standard library.
Summing an array
It’s super common to want to add up all the elements of an array. In Swift you can do this like this:
let total = array.reduce(0, +)
I think a functional concept like reduce()
is unnecessary complexity for something as simple as summing an array. Adding array.sum()
to the standard library is justified. The implementation would be something like this:
// This should be in the standard library
extension Sequence where Element: Numeric {
func sum() -> Element {
return reduce(0, +)
}
}// Now I can do this
let total = array.sum()
Regular expressions
Currently in Swift there is no language or standard library support for regular expressions. To use regular expressions you have to import Foundation
and deal with NSRegularExpression
which is not exactly a modern Swifty API.
Regular expressions were useful in several of the puzzles and Swift should certainly add regular expression literals to the language.
Uppercasing and lowercasing
Swift has string.uppercased()
and string.lowercased()
to return an uppercased or lowercased version of a string. But oddly there’s no way to uppercase or lowercase a Character
. I ended up converting my character to a string, uppercasing that, then converting the string back to a character, which is wasteful. Adding character.uppercased()
and character.lowercased()
to the standard library seems useful.
Update: Michael Ilseman from Apple kindly let me know that character.uppercased()
and character.lowercased()
have been implemented in Swift 5! They do return a String
not a Character
though, because Unicode.
Basic file I/O
The standard library does not provide any support for reading from disk. To do that, you have to import Foundation
and deal with Bundle
, FileManager
and friends. If you want to lazily read from a file (for example, to consume a file line by line without reading the entire contents into memory) then you also have to cope with FileHandle
. All of this is quite unpleasant and you will end up on Stack Overflow wondering why on earth it has to be so complicated.
There is nothing as simple as Python:
with open('filename.txt') as file:
for line in file:
print(line)
This gist (a top Google hit for “swift read file line by line”) shows the kind of pain you have to go through in Swift.
Creating 2D arrays
In general, Swift values verbosity and descriptiveness in API naming. Sometimes however, this can make code significantly less readable. Consider this:
C:
// Define a 5x5 array of integers
int array[5][5];
Swift:
// Define a 5x5 array of integers
var array = [[Int]](repeating: [Int](repeating: 0, count: 5), count: 5)
Admittedly Swift has initialized the elements to zero while C has not, but even so it is hard to argue that the Swift code is beautiful. The nested initializer is ugly.
Checking for non-empty collections
In Swift, you can check if a collection is empty using the isEmpty
property. But if you want to check if a collection is non-empty and you have a long line of code, things can get hard to read.
if !users.filter { $0.isActive && $0.highScore > 50 }.isEmpty
The !
and isEmpty
are a long way apart but must be understood together, which harms readability. It would be better if you could write:
if users.filter { $0.isActive && $0.highScore > 50 }.isNonEmpty
Now you can more easily see that we’re finding out whether there are any active users with a high score of greater than 50. It’s a simple matter of:
// I think this should be in the standard library
extension Collection {
public var isNonEmpty: Bool {
return !isEmpty
}
}
It would be nice if Swift had a few more of these small affordances.
String, String, String
Here’s a list of issues I found with the ergonomics of String
.
- If you want to find out if a string contains a particular character, you can call
str.contains(_ element: Character)
. But if you want to find out if a string contains a particular substring, you have toimport Foundation
and either callstr.contains(_ str: String)
onNSString
orstr.contains<T>(_ other: T) where T : StringProtocol
. It’s odd that the last one is part of Foundation and not the standard library. Finding a substring should be part of the standard library. - There is no method on
String
for trimming whitespace. You have toimport Foundation
and callstr.trimmingCharacters(in: .whitespacesAndNewlines)
. Can we havestr.trimmed()
please? Oh and by the way,.whitespacesAndNewlines
is aCharacterSet
which is a Foundation data type that is a completely different thing toSet<Character>
. - Likewise, there is no
str.chomp()
method for removing newlines from the end of a string. - Let’s say you have a string like
"-1, 3, 4, 7"
and you want to turn it into an array like["-1", "3", "4", "7"]
. The solution isstr.split(whereSeparator: { !"-0123456789".contains($0) })
. Hmm. The problem is that there is no version ofsplit()
which takes aSet<Character>
or aString
— there are only versions which take aCharacter
or thewhereSeparator
predicate. If there was a version which took aSet<Character>
you could writelet separators: Set = [",", " "]; str.split(on: separators)
. If there was a version that took aString
you could writestr.split(on: ", ")
.
Data structures
The collections offered by the standard library are quite limited. Only Array
, Set
and Dictionary
are available. Several times I wanted a Deque
but these aren’t included.
I hope that more data structures will be added to the standard library as I can imagine things like binary search trees and graphs would also be useful.
Repeatedly cycling through a Collection
If you have an Array
, you can iterate over it using for element in array { }
. What if you want to iterate over it infinitely though? You can do this, but it’s ugly:
loop:
while true {
for element in array {
// do something
if someCondition {
break loop
}
}
}
Python’s “itertools” module has cycle()
which generates the elements of an iterable infinitely. It would be nice if Swift had something similar.
Conclusion
I hope this post doesn’t come across as whinging — overall I think Swift is amazingly powerful and has huge potential to grow its use and applicability. I hope that progress can be made on fixing some of these points after Swift 5.
I would also recommend taking part in Advent of Code in future— the puzzles are interesting and don’t take too long to solve.