It's been a few months since I've written a public entry, but I've had a lot of personal growth meanwhile. A lot of this growth feels ineffable, but trust me when I say that being mindful and trying new things pays off. These are true clichés.
One of the new things I’ve been really enjoying has been getting started with iOS engineering. Having spent time in Unity (hundreds, maybe thousands, of hours 😬) before joining the workforce as a backend software engineer, working client-side feels familiar and nostalgic.
In particular, Swift has a lot in common with writing high-performance C#. In both cases, I want simple syntax and memory to be automatically managed; however, I still want to be close to the hardware.
Of course, there are differences too. Swift doesn’t have a garbage collector, and it’s statically compiled (caveats: .NET Native AOT, Unity IL2CPP).
As I was sitting in a nearby coffee shop, I wondered what it would take to convert some code I wrote earlier this year. I was working with bit arrays in C#, and often wanted to test the cases of 0, 1, and the maximum 64-bit integer, as well as exercise some stray integers in between. It seemed I could do this by iterating over the sequence of base 2 integers 0, 1, 11, 111, …, 1111111111111111111111111111111111111111111111111111111111111111.
IEnumerable
In C#, yield
can be used to create the sequence. A method returning IEnumerable<ulong>
can sequentially create and yield each bit array, and after the last value is yielded (when all the bits set to 1), it will halt.
static IEnumerable<ulong> PopCountProgression()
{
for (var i = 0UL;; i = (i << 1) | 1)
{
yield return i;
if (i == ulong.MaxValue)
break;
}
}
Alternatively, I can express this with Linq by calculating the bit array from the iteration count. (Note: left bit shift of 64 will perform a shift of 0, see ECMA-334 v7.0 §12.11 )
static IEnumerable<ulong> popCountProgression() =>
from i in Enumerable.Range(0, 65)
select i < 64 ? ((ulong.MaxValue << i) ^ ulong.MaxValue) : ~0UL;
Now, I can handle iteration idiomatically.
// Print from 0 through 18446744073709551615
foreach (var i in PopCountProgression())
Console.WriteLine(i);
// Print from 0 through 127 (the first 8)
foreach (var i in PopCountProgression().Take(8))
Console.WriteLine(i);
Sequence
In Swift, sequences are made by conforming to Sequence
and IteratorProtocol
. This isn’t like using a generator syntax, so I used the iteration count to calculate the bit array.
struct PopCountProgression: Sequence, IteratorProtocol {
private var progress = 0
mutating func next() -> UInt64? {
guard progress <= 64 else { return nil }
defer { progress += 1 }
return (UInt64.max << progress) ^ UInt64.max
}
}
for i in PopCountProgression() {
print(i)
}
Similar to C#, I can rewrite this with functional programming.
func popCountProgression() -> LazyMapSequence<ClosedRange<Int>, UInt64> {
(0...64).lazy.map { (UInt64.max << $0) ^ UInt64.max }
}
The type of the sequence is now more generic, and I can handle my sequence idiomatically, much like I would in C#!
// Print from 0 through 18446744073709551615
for i in popCountProgression() {
print(i)
}
// Print from 0 through 127 (the first 8)
for i in popCountProgression().prefix(8) {
print(i)
}
Wrap-Up
While Swift doesn’t have generators or Linq, it does have great support for functional programming. It was interesting to explore and see what’s possible in Swift. Despite being a younger language, it does seem like much of what I’ve done in Unity and C# will prove relevant with Swift.
See the complete code on GitHub Gist.
Further Thoughts
Do the C# solutions cause heap allocations? Does it cause heap allocations in Unity IL2CPP? What other complexity could it be hiding?
Since the Swift compiler is based on LLVM, would an iterator like this ever lead to auto-vectorization? Is there a possibility that the Swift Intermediate Language expresses a vectorizable loop?
What other protocols are useful for this sequence to conform to in Swift?