Bonus Round – Spanning Sequences in Swift 3.1

Last week, some of you may have noticed that our groupByUser function used the same "test" closure twice, once for prefix(while:) and once for drop(while:). It kind of seems like these two are doing the same thing, right? We are splitting the array at a certain point; one time we want the subsequence before the split, the other time we want the subsequence after the split. In our case, we actually want both.

I know what you're thinking, and yes, there is a function for this! In languages like Haskell and Scala, it's called span. It doesn't exist in Swift yet, but we can make one ourselves with the new sequence methods we just talked about.

extension Sequence {
  func span(_ p: (Iterator.Element) throws -> Bool) rethrows -> (SubSequence, SubSequence) {
    return (
      try self.prefix(while: p),
      try self.drop(while: p)
    )
  }
}

This makes our groupByUser function much cleaner:

func groupByUser(_ messages: [Message]) -> [[Message]] {
  guard let firstMessage = messages.first, messages.count > 1 else {
    return [messages]
  }

  let (firstGroup, rest) = messages.span { $0.user == firstMessage.user }
  return [Array(firstGroup)] + groupByUser(Array(rest))
}

👌

2 Comments Bonus Round – Spanning Sequences in Swift 3.1

  1. Richard Critz

    Where do the `throws` and `rethrows` come from in your declaration of `span`? No evidence of such in the predecessor post which is why I’m confused. Thanks!

    1. ross

      The full signature of drop(while:) is public func drop(while predicate: (Iterator.Element) throws -> Bool) rethrows -> SubSequence. It’s basically saying that if you pass in a predicate that throws, it can re-throw that error. Since span is building on this, we also should accept predicates that throw. However, we don’t want to handle the errors ourselves, since we don’t know what kinds of errors might be possible, so we re-throw the error to the caller, who should be aware of what errors the predicate it passes in can throw.

      You can see the code for drop(while:) here, and the code for prefix(while:) a little below it.

Comments are closed.