Figure

A blog about Swift and iOS development.

First and Rest

Often times (especially when writing recursive functions) we want to take an array and split it into its first and remaining elements. The most obvious way of doing this in Swift is pretty clunky:

//Ugly!
func loop(_ c: [Int]) {
  guard !c.isEmpty else {
    return
  }
  var rest = c
  let first = rest.removeFirst()

  print(first)
  loop(rest)
}

I hate pretty much everything about this.

First, removeFirst() requires we make sure the array isn't empty. But it's non-obvious we're supposed to care about that. In fact, we probably shouldn't care. We want to focus on first, but our code is forcing us to think about the mechanism we're using to retrieve it instead.

Then there's the assignment of c to rest just to make it mutable. And the fact that rest is named "rest" even though, initially, it contains the entire array. It's not clear at all that removeFirst() returns the value it removes, so the value of first is something of a mystery. And, when all is finally said and done, rest is mutable even though it has no reason to be.

All and all, it feels too verbose, and way too imperative. Nothing apart from the names first and rest give any hint about what's going on here. Thankfully, there's another way to skin this cat:

//Pretty!
func loop(_ c: [Int]) {
  let (first, rest) = (c.first, c.dropFirst())
  guard let someFirst = first else {
    return
  }

  print(someFirst)
  loop(Array(rest))
}

This is better on a number of levels. Nothing is ever mutable, for one. And c can now be empty making first optional — which lets us focus on our value and its existence rather than implementation details of our array. And first and dropFirst() are straight-forward in their naming and behavior.1

There's only one thing still sticking in my craw. first is used for one hot minute before being superseded by the the unwrapped someFirst. Depending on our sensibilities, this might be something the nascent "unwrap" proposal could help with. In the meantime, though, it looks like a job for case:2

//Concise!
func loop(_ c: [Int]) {
 guard case 
       let(first?, rest) = (c.first, c.dropFirst())
       else {
   return
 }

 print(first)
 loop(Array(rest))
}

And there we go! Clear, concise assignment through tuples and just-in-time binding thanks to case.

Back when we left ObjC, many of us asked ourselves, "How much more expressive can Swift really be?" My answer is, "Very; and increasingly so."


1: Well, almost. Note that dropFirst() returns an ArraySlice. Because loop expects an Array, we have to convert rest before passing it to loop(). Reworking loop() to take a generic Collection would get around this — at the expense of being less blog-friendly.↩︎

2: For those unclear on the work first? is doing here, this post on optional pattern matching covers the basics.↩︎


Hit me up on twitter (@jemmons) to continue the conversation.

Latest | Archives