Holy War Part II: Son of Last Item in a Non-empty Array

Here are the two use cases motivating my Last Item in a Non-empty Array post. You can see the entire gist here.

The first method uses the guarantee of a non-empty array and notes it in an associated traditional comment. I have no problem with this. I should note that this second method is defined right after the method it builds on, and there’s little likelihood of confusion or review issues.

public func partitioned(where predicate: @escaping (_ element: Iterator.Element, _ mostRecent: Iterator.Element) -> Bool ) -> [[Iterator.Element]] {
    // `partitioned(at:)` guarantees a non-empty array
    // as its second closure argument
    return self.partitioned(at: {
        return predicate($0, $1.last!)
    })
}

The more serious problem is when you use the `partitioned(at:)` call outside the enclosing type. For example, you might want to partition an array at each point where the value changes:

testArray.partitioned(at: { $0 != $1.last! })

Looking at this one line of code, you have to make not just one but two mental leaps.

  • First there’s the correlation between the anonymous arguments and the call. Unlike partitioned(where:), which can be called for example with !=, this partitioned(at:) call takes two arguments, one of which is an element, and another an array of elements.
  • Second there’s the fact that there are no clues or information that the second anonymous argument is guaranteed to be non-empty, except if you note this in a comment or assertion or something like that.

If you use Xcode QuickHelp from this freestanding line of code, the non-empty guarantee is not visible:

The guarantee is only visible when used directly at the definition site:

Further, there’s a natural Swift coder tendency to question every exclamation point, namely: “Is this an intentional unwrap, with an expectation of failure should the unwrapping fail, or is it a naive use to shut up the compiler?”

Moving from forced unwrap to the lastOrDie workaround I mentioned in my original post makes user intent clear and explicit, and differentiates last from lastOrDie in Xcode’s pop-up completion list. It says: “This code should break on an empty array, which should never happen”. There’s a guarantee in place, implied by using lastOrDie.

You can mitigate forced unwraps with comments. (There’s an ongoing holy war about whether these kind of comments are appropriate or themselves bugs. Hi Soroush!)

// $1 is guaranteed as a non-empty array
testArray.partitioned(at: { $0 != $1.last! })

Or you can make the closure big and explicit. You might incorporate  an assert (will be removed from production code) or guard statement (won’t be), but this gets kind of ridiculous when you just want to force unwrap an item you know is going to be valid. Compare this mess to the original $0 != $1.last! solution:

testArray.partitioned(at: {

    (element: Int, currentPartition: [Int]) -> Bool in
    assert(!currentPartion.isEmpty, "Partition guaranteed to be non-empty")
    element != currentPartition.last!
})

Hopefully this provides the context missing from my original post. If you still have questions about why and how this is a problem, tweet, comment, or email. Thanks.

One Comment

  • Having a non empty array type similar to Ceylons non empty Iterable&less;T, Nothing>, abbreviated {T+}, or non empty Sequence&less;T>, abbreviated [T+] (as opposed to the potentially empty Sequential&less;T>, abbreviated [T*]) would be really helpful here.