Hello there, my sweet fellow developers,
Lets take a look at a completely different case of duplication. Suppose we want to perform serialization of an object:
1 2 3 4 5 |
let keyedArchiver = NSKeyedArchiver() keyedArchiver.encode("value", forKey: "key") keyedArchiver.encode("value1", forKey: "key1") keyedArchiver.encode("value2", forKey: "key2") |
For the sake of clarity lets imagine that we want it specifically like that in several lines. We’ll discuss a better approach later on, but without adding such constraint it would be hard to show some useful tricks:
1 2 3 4 5 |
let keyedArchiver = NSKeyedArchiver() keyedArchiver.encode("value", forKey: "key") keyedArchiver.encode("value1", forKey: "key1") keyedArchiver.encode("value2", forKey: "key2") |
That’s you average standard code for using the keyed archiver. The problem with it is, that it’s hard to refactor or extend it. Moreover, there is a duplication, as we call encode multiple times, which sucks in my humble (not really) opinion. And that duplication is not minor. Just imagine, that we’d want to use a different variable name and method for encoding. We’d have to rewrite the whole code to do so. A better solution would be to remember, that functions could capture the context around them:
1 2 3 4 5 6 7 8 |
func averageEncode(_ value: Any?, forKey key: String) { keyedArchiver.encode(value, forKey: key) } averageEncode("value", forKey: "key") averageEncode("value1", forKey: "key1") averageEncode("value2", forKey: "key2") |
We just use a local higher order function in that case. So, if we want to change the method name and variable, that would be done in just one place, which would help us avoid confusion. The problem with this solution is, that we’d have to write such an encode in any encoding function around the project, and that’s an unacceptable case of the duplication. So, how about we make a factory of functions for encoding, that would catch the variable passed to them?
1 2 3 4 5 6 7 8 9 10 |
typealias EncodeFunction = (Any?, String) -> () func encoding(coder: NSKeyedArchiver) -> EncodeFunction { return { coder.encode($0, forKey: $1) } } let betterEncode = encoding(coder: keyedArchiver) betterEncode("value", "key") betterEncode("value1", "key1") betterEncode("value2", "key2") |
That’s much better. If Swift allowed generic functions without type specification to be put in variables, we could improve the code even more. However, it doesn’t allow. One fundamental flaw in that approach is that we have to rely on external type specific functions and we don’t need that. Why? Because Swift functions are higher order. This means, that we can put them into variables, return them from functions or pass them as parameters to functions:
1 2 3 4 5 6 7 8 9 |
extension NSKeyedArchiver { typealias ArchiveFunction = (Any?, String) -> () } let archive: NSKeyedArchiver.ArchiveFunction = keyedArchiver.encode archive("value", "key") archive("value1", "key1") archive("value2", "key2") |
Sadly, that approach doesn’t always work. E.g., when the function is a struct mutating function or when the function has default parameters in it and several signatures with the same name (say, DispatchQueue.async signatures). Kudos to swiftc team, I hate you. Still, as we discussed in previous rants, calling a function multiple times in one scope means, that the solution wasn’t decomposed well enough. And we should definitely remember, that there are extensions, which allow us to add new ways of expressing our needs without bloating our code with helper classes (kudos to Java):
1 2 3 4 5 6 7 8 9 |
typealias Archive = [String: Any?] func values() -> Archive { return ["value": "key", "value1": "key1", "value2": "key2"] } keyedArchiver.encode(values(), forKey: "key") |
BTW, this is a learning example, don’t forget to put the magic strings into constants, as we discussed in one of the previous blog posts.
I’ll discuss higher order functions some time later in more detail. Try them out, send in your cases of code to discuss or complain on your colleagues.
That’s all, folks. Have a great day and stay DRY, no matter, where you are.