I like to look at other programming languages for inspiration. Most of us work in a single language on a daily basis. We start aligning our thought process to that language. We settle into certain tropes and styles based on the technical limitations of our language. Exploring other languages can introduce us to new concepts— like the simple conveniences in Ruby’s syntactic sugars, or the complex metaprogramming available in C++’s templates. In this post, we’ll take a look at one of Python’s features known as sequence unpacking.
Python has this little beauty of code to swap variables a and b:
This is Python’s sequence unpacking. It’s obvious, concise, and extensible. Sequence unpacking goes by several names in other languages— ‘destructuring bind’ in Lisp or ‘parallel assingment’ in Ruby. We’re going to dive deeper into this feature with an Objective-C hat on. There are three typical uses for sequence unpacking:
- Easy swapping is simple, elegant, and rids us of a temporary variable cluttering up things. Here’s swap without sequence unpacking:
1 2 3 4 5
- Multiple return values ease the calee, so it can return distinct values without having to generate a new type or pass things by reference through extra parameters. Consider how you delete files:
The remove method communicates two things— whether the file was removed, and detailed error information. With sequence unpacking you could do something similar:
1 2 3
- Parsing out complex types eases the caller, so it can work directly on the needed data. Imagine you want to find the area of a UIView. You have to grab the bound’s size and then query for its width and height:
1 2 3 4
With sequence unpacking you could move to a more concise version:
1 2 3
The problem sequence unpacking solves is not huge. We make due in all three of these use cases with Objective-C. What sequence unpacking does is let us express our true intent succintly.
We all realize that when swapping two variables, we need to copy one value temporarily. Does that mean we need to explicitly write that? We know that you can simulate multiple return values with out parameters. But should we be forced to hide our intent through inderection? When we recieve a composite type, sure we can store it away and separately access its members. But why can’t we just directly recieve what we want in the format we want?
I’ll first declare the ideal syntax for sequence unpacking in Objective-C:
Fat chance of that. Here’s the best we can do:
This syntax makes it look like we’ve introduced two new keywords—
from. In reality
from is just
#defined from ,, so we’re left with:
unpack is a function that takes something to pack up and somewhere to unpack it.
1 2 3 4
Two protocols define what’s required to make something packable or unpackable.
1 2 3 4 5 6 7 8 9 10 11
If you want your class to be packable, then implement
pack and return an enumerator. To allow unpacking, just implement
unpack:, and you’ll recieve the enumerator that the packer created.
With these definitions, we have a flexible sequence unpacking system. The protocols let us easily support both packing and unpacking to/from arbitrary classes. With enumerators, classes can pack into any number of slots. Finally, the unpacker can unpack a different number of slots than the packer packed. For example,
unpack (_(x,y) from Get3DPoint()) would only unpack the x and y coordinates of a three dimensional point and ignore the z coordinate.
You probably are wondering what this
_(...) thing is.
_ is a macro for easily creating a Tuple. What’s a Tuple? It’s a fixed and ordered list of elements. It’s a lightweight array. Having the
_ macro for a Tuple makes it nice for packing up objects without having to create a custom class to contain them since Tuple implements the PAKSeqPacker protocol.
Here, we’ve packed up 1 and 2 easily by placing them into a Tuple, and letting the Tuple take care of packing them.
It’s more interesting when we want to easily unpack values:
_ macro Tuple is on the left of the
from, it acts as a simple container for unpacking variables. In the exmaple above, we want to unpack a value into the x variable. The Tuple needs to store a pointer to x. Passthrough Tuples store pointers to their contained objects. This allows them to read the contained variables’ values if the Passthrough Tuple will be packed, or write to the variables if the Passthrough Tuple will be unpacked.
So not only is
_ acting as a shorthand for creating Passthrough Tuples, it’s also taking its parameters by reference instead of by value. It does this like so:
The definition of
_ above is limited to only two parameters. Ideally, PassthroughTuple should take any number of parameters. The solution for this is not pretty.
1 2 3 4 5 6
We create a macro for creating a PassthroughTuple with one argument and call it
PT_1. We make another one for two arguments and call it PT_2. We do this all the way up to
Given these 10 macros, we then need a way to define an
_ macro which can, based on the number of arguments, call the appropriate
PT macro. We can do this by using this excellent trick.
Many Foundation classes can trivially be extended to support sequence unpacking.
For example, here’s how we make NSArray packable:
1 2 3 4 5 6
With Foundation support, we can now do:
1 2 3
This sequence unpacking implementation only supports objects. It can’t pack primitives like structs. This is unfortunate since one of the examples I gave was to unpack a CGSize.
The only way to do this is to wrap the struct in an NSValue, and make the values you unpack into
id (unpacked as NSNumbers).
1 2 3
This requires us to add packing support to NSValue:
1 2 3 4 5 6 7 8 9 10 11
Even cleaner in C++
Turning an eye towards another language, C++, it should be entirely possible to duplicate this work in C++. What could be even more interesting is to take advantage of C++’s operator overloading. In C++, classes can define implementations for operators like +, –, etc. With that tool, I think you can make sequence unpacking as simple as:
If you are comfortable giving up the = operator to sequence unpacking, then you could even do:
That’s pretty amazing.
Sequence unpacking is a convenient feature in other languages, and I wanted to see how we could bring that to Objective-C in the most concise way possible. The solution is flexible and functional, but has its limits. To make it look integrated into the language, I defined
from to be a
,, which isn’t ideal. This solution also struggles with primitives, having to wrap them in NSValues. However, it’s really interesting to see how these sorts of features can be brought to an existing language. I hope you enjoyed this exercise, and feel free to get in touch if you have any thoughts.