- Proposal: SE-0094
- Authors: Kevin Ballard, Erica Sadun
- Status: Implemented in Swift 3 (Rationale, Bug)
- Review manager: Chris Lattner
- Previous proposal: SE-0045
- Revision: 2
- Previous Revision: 1
This proposal introduces sequence(first:next:)
and sequence(state:next:)
,
a pair of global functions that return (potentially-infinite) sequences of lazy
applications of a closure to an initial value or a mutable state.
Swift-evolution thread: Discussion thread topic for that proposal
SE-0045 originally proposed iterate(_:apply:)
, a method that
was subsequently changed to unfold(_:applying:)
. The proposal was
accepted with modifications, rejecting unfold
based on naming
alone. As its core utility remains unquestionably high, this proposal
re-introduces the method with better, more Swift-appropriate naming.
This function provides the natural counterpart to reduce
as well as a
replacement for non-striding C-style for
loops that were removed by the
acceptance of SE-0007.
sequence
can be used to apply generation steps that use non-linear math or
apply non-mathematical operations, as in the following examples:
for x in sequence(first: 0.1, next: { $0 * 2 }).prefix(while: { $0 < 4 }) {
// 0.1, 0.2, 0.4, 0.8, ...
}
and
for view in sequence(first: someView, next: { $0.superview }) {
// someView, someView.superview, someView.superview.superview, ...
}
The declarations for the proposed functions look like:
public func sequence<T>(first: T, next: T -> T?) -> UnfoldSequence<T>
public func sequence<T, State>(state: State, next: (inout State) -> T?) -> UnfoldSequence<T>
Both functions return potentially-infinite sequences of lazy repeated applications of a function to an initial value or a state.
The first function, sequence(first:next:)
, yields the first
value, followed
by a series of values derived from invoking next
using the previous value.
The yielded sequence looks like [first, next(first), next(next(first)), ...
.
This sequence terminates when the next
function returns nil
. If the
function never returns nil
the sequence is infinite. This function is
equivalent to Haskell's iterate
, however the Swift version
is not always infinite and may terminate.
The second function, sequence(state:next:)
, passes the state
value as an
inout
parameter to next
and yields each subsequent return value. This
function is equivalent to Haskell's unfoldr
, though we've
chosen to make the state an inout
parameter instead of returning a new state
as this is less likely to produce unwanted Copy on Write (COW) copies of data
structures.
Both functions return a sequence type named UnfoldSequence
. Existing Swift
naming conventions would call this SequenceSequence
. Using UnfoldSequence
instead resolves the unwarranted redundancy and provides a meaningful reference
to developers familiar with functional programming languages.
None, this change is purely additive.
The natural name for sequence(state:next:)
is unfold
. Functional
languages that offer unfold
pair it with fold
, which has already been
established in Swift as reduce
. Renaming reduce
has already been rejected.
The name sequence
best describes this function in Swift. unfold
on its own
is not descriptive and has no meaning to developers not familiar with functional
programming languages.
The function sequence(first:next:)
can be expressed using
sequence(state:next:)
. We include it in this proposal due to this form's high
utility. Correctly reimplementing this form in terms of sequence(state:next:)
is non-trivial; the simple solution is more eager than it should be.