Consider the Following
Van Simmons
|
3 min readHi, my name is Van Simmons and I’m a team lead here at AO Labs. Welcome to my new blog where we’ll be working in some corners of the Apple ecosystem that don’t seem to be all that heavily frequented. The idea behind this blog and the inspiration for the title is that every couple of weeks I want to take some seemingly innocuous structure in Swift and work out some of the implications of its use by considering what such a structure really means. Particularly we’ll be exploring important programming concepts through the use of Swift’s “generics” system and where those concepts lead us when actually implementing applications and libraries.
My role at AO Labs involves pondering the future of technologies like spatial computing, machine learning/AI, and functional programming, especially the latter. So the particular structures I want to consider are going to lie at the intersection of those things. In particular we are going to be examining how to build composable structures in Swift that address real problems in those areas. So let’s get started with something simple.
Consider the Following:
func doubler(_ anInt: Int) -> String {
" \(anInt * 2) "
}
The doubler
function is incredibly simple, it takes an Int
as an argument, doubles it, encloses the doubled value in 4 leading and 4 trailing spaces and returns it. It’s hard to imagine anyone actually using something like this, which is in fact, part of the point: we’re not actually interested in what the function does, we’re interested in it’s structure. But what precisely do I mean by that?
Frequently, when doing code reviews, I’ll comment something along the lines of: “this would be more readable as an init
on the return value”, or “it probably make sense do this as a computed var”. Colleagues who have not worked with me before generally have to ask what I mean by that. What I mean is that some particular bit of code should use a different form of the same structure.
Consider the following forms:
extension String {
init(doubler anInt: Int) {
self = " \(anInt * 2) "
}
}
extension Int {
var doubler: String {
let anInt = self
return " \(anInt * 2) "
}
}
Notice any similarity in those three different forms? Yeah, they all include the following code fragment verbatim:
" \(anInt * 2) "
What’s going on here is that Swift gives us multiple different ways of expressing these code fragments. In the introduction to his paper “Generalising Monads to Arrows”, John Hughes writes:
One of the distinguishing features of functional programming is the widespread use of combinators to construct programs. A combinator is a function which builds program fragments from program fragments; in a sense the programmer using combinators constructs much of the desired program automatically, rather than writing every detail by hand.
What this means in Swift is that all of three forms above can be written in terms of each other. For example the init and the computed var can be written as wrappers around the func form like so:
extension String {
init(doublerF anInt: Int) {
self = Playground.doubler(anInt)
}
}
extension Int {
var doublerF: String {
Playground.doubler(self)
}
}
So, the code review comments I mention above are saying, in effect: “you can make this code more readable by converting to one of the other forms”. But aren’t there things you can’t do, if you put your code in one of the other forms? The answer is nope, anything you can do in one form, you can do in the other. Technically we say that each of those 3 forms is isomorphic to the other 2.
When two types of things are isomorphic, it means that you can take any instance of one type, run a mechanical process which will produce an instance of the second type, take that second instance and run an inverting mechanical process and get back the exact same instance you started with.
Generally we speak of some Swift types as being isomorphic, the interesting thing here is that we are actually talking about code isomorphisms: I can mechanically convert from any of the forms above to the other and then mechanically convert back to get exactly the same thing I started with.
Next time, we’ll go into some detail on precisely how Swift lets you use these isomorphism and explain some syntax that you may not be aware of if you are not accustomed to programming Swift in the “point-free” functional style.