Proposal Spotlight: SE-0045 Add scan, takeWhile, dropWhile, and iterate to the stdlib

While SE-0045 hasn’t yet been scheduled for review, it’s a proposal  I’m really looking forward to. Lily Ballard introduces several standard library functions to support sequence operations. Commonly found in other modern languages like C#, Haskell, and Scala, the proposal includes three new sequence functions (scan(_:combine:)takeWhile(_:), and dropWhile(_:))  and a global iterate(_:apply:) function.

The scan function successively applies a function across the members of a sequence. It uses a rolling result and the next sequence value to create each new member of the output sequence. So you might, for example, create an ever-updating sum or product:

extension SequenceType {
  /// Returns an array containing the results of
  ///
  ///     p.reduce(initial, combine: combine)
  ///
  /// for each prefix `p` of `self` in order from shortest to longest, starting
  /// with the empty prefix and ending with `self`.
  ///
  /// For example:
  ///
  ///     (1..<6).scan(0, combine: +) // [0, 1, 3, 6, 10, 15]
  ///
  /// - Complexity: O(N)
  func scan<T>(initial: T, @noescape combine: (T, Self.Generator.Element) throws -> T) rethrows -> [T]
}

You can think of scan as a brother of reduce, but one that outputs a sequence of intermediate values along the way.

The iterate function in particular is one that many Swift developers have been clamoring for, as it gives you a simple and obvious way to create mathematical progressions that extend beyond simple “n, n + m, n + 2*m, …” strides:

/// Returns an infinite sequence of lazy applications of `apply` to the
/// previous value. For example:
///
///     iterate(1, apply: { $0 * 2 }) // yields: 1, 2, 4, 8, 16, 32, 64, ...
func iterate<T>(initial: T, apply: T -> T) -> IterateSequence<T>

The two “while” functions respectively return the longest prefix of elements that satisfy a given predicate (takeWhile), and the corresponding suffix (dropWhile).

protocol SequenceType {
  // ...
  /// Returns a subsequence by skipping elements while `dropElement` returns
  /// `true` and returning the remainder.
  func dropWhile(@noescape dropElement: (Self.Generator.Element) throws -> Bool) rethrows -> Self.SubSequence
  /// Returns a subsequence containing the elements until `takeElement` returns
  /// `false` and skipping the remainder.
  func takeWhile(@noescape takeElement: (Self.Generator.Element) throws -> Bool) rethrows -> Self.SubSequence
}

Combining iterate with takeWhile basically introduces a Swift-er version of the C-style for loop. Here are a couple of examples that show how these functions would look like in practice.

for x in iterate(0.1, apply: { $0 + 2 })
    .takeWhile({ $0 < 10 }) 
{
    // ... 0.1, 2.1, 4.1, ...
}

and

for view in iterate(startingSubview, apply: { $0.superview })
    .takeWhile({ $0 != nil }) {
    // ... view, view.superView, view.superView.superView ... 
}

Like for loops, each example has a starting value, a closure that lazily updates that value for each iteration, and a sequence generator that produces values until its predicate returns false. While you can convert for loops using Swift’s existing do and while statements, both iterate and takeWhile provide a natural, fp-style correspondence.

When you add in the new SE-0065 collection and index updates, the upcoming stride function and range operator revisions, and floating point math fixes, all of which are under active proposal development,  then pretty much all the C-style for loop inadequacies are addressed in a modern, Swift-native style.

Comments are closed.