Programming Language Abstractions I Don't Want To Go Without
Programming languages, like everything in tech, are constantly evolving. Newer abstractions ease the burden of programming. Here are the ones I like:
Garbage Collection
Using a non-garbage-collected language like Swift makes it clear how much more work you have to do, such as identifying weak references, and dealing with memory leaks and crashes caused by premature deallocation.
Extensions
Extensions in Swift let you add convenience functions to existing types. For example, in Futurecam, a camera app, users who don’t pay get a watermark. I implemented this by adding a function saveWithWatermark() to the iOS UIImage class, which adds a watermark to the image and saves it to the photo gallery. This function is implemented by converting the image into a buffer, creating a graphics context to draw onto it, drawing the watermark, and invoking the iOS Photos framework to save it to the photo library, prompting the user for permission if needed. All this complexity, which spans multiple subsystems, is encapsulated in one function in an existing class. Code in languages like Java that don’t have extensions is often littered with FooUtil classes for every Foo. Every time you use it, you have to stop and think whether to invoke it as foo.doSomething() or FooUtil.doSomething(foo).
IIFE
… for initialisation of fields: I like Swift code like
let videoDataOutput: AVCaptureVideoDataOutput = {
let vdo = AVCaptureVideoDataOutput()
vdo.videoSettings = kCVPixelFormatType_32BGRA
vdo.alwaysDiscardsLateVideoFrames = false
return vdo
}()
If a class has a bunch of fields like this, the initialisation for each field is self-contained, rather than having a 60-line constructor, which is hard to read. And risks the possibility that a reordering causes a field to be accidentally used before it’s fully initialised. On the other hand, the IIFE-based initialisation ensures that the field is fully initialised before it’s used.
This also works for global variables. And the IIFE is invoked when the global variable is first used, which makes it deterministic. Unlike (say) C++, which has undefined order of global variable initialisation.
Properties
In languages like Swift and Python, you can have something that looks like a field but when you try to access it, a getter or setter is automatically invoked. Languages like Java that don’t have this force you to write a lot of boilerplate like:
class Person {
public String getName() { return name; }
public void setString(String name) { this.name = name; }
private String name;
}
With properties you can write:
class Person {
var name: String
}
and it’s a stored property.
Later, if you want to change its implementation, you can change it to:
class Person {
var name: String {
get { return json.get("name") }
set { json.set("name", newValue) }
}
}
Now it’s a computed property. Callers won’t have to change.
Some languages like also have a didSet, which is a callback that’s automatically invoked by the language when the value is assigned. For example, in a camera app with different modes like photo, video and timelapse, you can have a didSet on the mode that configures the camera for the appropriate mode.
Guard
In Swift instead of writing
if !enabled { return }
You can write
guard enabled else { return }
This seems trivial, but the benefit is that the compiler ensures that the else clause returns. This matters when it’s complex. For example:
func save(withWatermark: Bool) {
guard withWatermark else {
let data = CFDataCreateMutable(nil, 0)!
let uTType = globalCurrentMode === BurstMode.self
? kUTTypeJPEG : "public.heic"
guard let destination = create(data, uTType, 1, nil) else {
return
}
CGImageDestinationAddImage(destination, self)
if !CGImageDestinationFinalize(destination) {
Analytics.logEvent("burst_image_save_destination_error")
}
PHPhotoLibrary.save(photo: data, completion: completion)
return
}
UIImage(cgImage: self).save(completion: completion)
}
Here, when you read the first guard statement, you can skip over the complex code in the braces, knowing that it will return, despite the nested guards and ifs. If you didn’t have a guard statement, you’ll have to check all cases to understand that the guard returns.
Private set
Languages have private and public access control levels, but private set is an intermediate access control level that lets other classes read but not write the variable. Languages that don’t have it make you write a boilerplate getter coupled with a private variable to produce the same result.
Keyword arguments
… make it readable, like:
transferMoney(fromAccount: account1, toAccount: account2)
rather than:
transferMoney(account1, account2)
which leaves it ambiguous whether the money is being transferred from account1 to 2 or the other way around.
String Interpolation
There’s no going back from:
print("Ordered \(item_count) items worth \(amount).")
to
print("Ordered " + item_count + " items worth " + amount + ".")
Inline Functions
Defining a function inline, also called closures, or lambdas in Python and Java, is a huge convenience.
Trailing Closure Syntax
When the last argument of a function is a closure, you can omit the parentheses. Instead of writing
camera.requestPermission(callback: {
let photo = camera.takePhoto()
photo.save()
})
you can write
camera.requestPermission {
let photo = camera.takePhoto()
photo.save()
}
It’s clearer, and it feels like defining your own syntax.
Parameters that are evaluated under control of the called function
On rare occasions, you want functions that only conditionally evaluate their arguments. For example, an assert() function that checks a condition and if it fails, logs a message to the server. You may not want this function to evaluate the message every time, to minimise overhead. Languages like Swift have a facility that marks arguments as not evaluated when the function is called. The evaluation is under the control of the function — the argument can be evaluated zero, one or many times. For example, you can define your own loop repeat(n) that repeats the code n times.
defer
… is just a better finally. You can write:
a = new File(...)
defer { a.close() }
b = new File(...)
defer { b.close() }
// Use a and b.
This is more readable than the equivalent code with finally:
File a = null
File b = null
try {
a = new File(...)
b = new File(...)
// Use a and b.
} finally {
if a != null { a.close() }
if b != null { b.close() }
}
With finally, the close() call is separated from the code that opens the file, so it’s harder to check that every open is matched by a close. Even if you check it, it can change over time as the code is updated. With defer, it’s obvious, since it’s in the next line.
The variable has to be declared outside the try to be in scope in finally. But you don’t have an initial value for it, so you need to initialise it to null. This is a bad idea — it’s better to declare variables only when their initial value is available. This happens naturally with defer. And in finally, you have to check if they’re null, or the error-recovery code will cause an error!
With defer, the compiler ensures that only files that are opened are closed. If something goes wrong and the function exits before opening b, the defer block won’t be called since control hasn’t even reached the defer. The language tracks all three cases — both files are opened (in which case both need to be closed), one file is opened (in which case only one needs to be closed), and none were opened (in which case none need to be closed). With finally, you have to track all three cases yourself via the != null checks.
Further, try introduces braces and nesting and breaks the flow of code.