3

Background & details

Swift evolution proposal SE-0094 was implemented in Swift 3.0, introducing the global sequence functions:

The latter is declared as follows

func sequence<T, State>(state: State, 
                        next: @escaping (inout State) -> T?) ->
         UnfoldSequence<T, State>

and is implemented in swift/stdlib/public/core/UnfoldSequence.swift. The language reference gives the following example for using it (note the lack of explicit type annotation)

// Interleave two sequences that yield the same element type
sequence(state: (false, seq1.makeIterator(), seq2.makeIterator()), next: { iters in
  iters.0 = !iters.0
  return iters.0 ? iters.1.next() : iters.2.next()
})

I cannot, however, get the example above to work (e.g. using let seq1 = 1...3, let seq2 = 4...6), but is prompted with the rather curious error message

error: ambiguous reference to member 'sequence(first:next:)'

Only if I explicitly type annotate the mutable State parameter in the next closure, as well as the return type of it, does the example above compile

let seq1 = 1...3
let seq2 = 4...6

for i in sequence(state: (false, seq1.makeIterator(), seq2.makeIterator()), 
        next: { (iters: inout (Bool, ClosedRangeIterator<Int>, ClosedRangeIterator<Int>)) 
                        -> Int? in
    iters.0 = !iters.0
    return iters.0 ? iters.1.next() : iters.2.next()
}) {
    print(i)
} // 1 4 2 5 3 6

This is not the way I hope to use sequence(state:next:), however, as I'd rather see it in on-the-fly applications where type inference works as it should, avoiding all the explicitness.

Question

  • Are we intended to use the sequence(first:next:) function with explicit type annotations as above? Is there some limitation in this function due to the inout parameter closure, or am I missing something?
Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • @Hamish thanks, that bug report would go in line with this behaviour. Thanks for the other point about the generic specializations, was just in the process of removing that point (due to other reasons)! At the very least I guess the ref. docs for `sequence(state:next:)` should be updated to include the explicit annotation of the `inout` parameter, quite confusing to attempt to make use of the function otherwise, if just leaning back on the docs (and not the broad SO contributers knowledge!). – dfrib Oct 23 '16 at 10:37
  • @Hamish (if you'd like,) you could probably post the information in your comment as an answer, it seems cover the most of explaining the "limitations" ask query about in my question. – dfrib Oct 23 '16 at 12:59
  • 1
    I've gone ahead and posted an answer :) Although unfortunately I wasn't really able to discover else besides what I said in my original comment. – Hamish Oct 23 '16 at 15:31
  • 1
    I've got a fibonacci implementation at the end of this section of my book http://www.apeth.com/swiftBook/ch05.html#_for_loops and I had to use explicit type annotations. But I didn't mind. :) [BTW I edited your title because it turns out the q. is really just about the type inference issue] – matt Oct 23 '16 at 15:50
  • @matt thanks for your edit and your answer! – dfrib Oct 23 '16 at 16:33

2 Answers2

2

This looks like a combination of two issues.

The first is that Swift currently doesn't infer the type of a multi-line closure without any external context. This is however intended behaviour, as confirmed by Apple developer Jordan Rose in the comments of SR-1570:

This is correct behavior: Swift does not infer parameter or return types from the bodies of multi-statement closures. But the diagnostic could be a lot better.

Therefore in theory, you would just need to explicitly define the return type of the closure you pass to sequence()'s next: parameter, as the parameter type can be inferred from external context (namely the type you pass into the state: parameter):

let seq1 = 1...3
let seq2 = 4...6

let combined = sequence(state: (false, seq1.makeIterator(), seq2.makeIterator()),
                        next: { iters -> Int? in
    iters.0 = !iters.0
    return iters.0 ? iters.1.next() : iters.2.next()
})

(Edit: This now compiles in Swift 3.1)


However, this still doesn't compile – which is due to the second issue, where the compiler cannot infer the type for an inout closure parameter in Swift 3 (which wasn't the case in Swift 2). This is a suspected bug, which has already been filed (see both SR-1976 & SR-1811).

Therefore, as you note in the question, this means (quite unsatisfactorily) that you have to explicitly annotate the full closure signature that you pass to next::

let combined = sequence(state: (false, seq1.makeIterator(), seq2.makeIterator()),
                        next: { (iters: inout (Bool, ClosedRangeIterator<Int>, ClosedRangeIterator<Int>)) -> Int? in
    iters.0 = !iters.0
    return iters.0 ? iters.1.next() : iters.2.next()
})
Hamish
  • 78,605
  • 19
  • 187
  • 280
  • Also, I would definitely recommend filing a bug with Apple over that documentation example that doesn't compile. – Hamish Oct 23 '16 at 15:30
  • Okay, but let's be fair. You could use a type alias and trailing closure syntax and make that code look a _lot_ nicer. You are kind of deliberately making it look awful. – matt Oct 23 '16 at 15:54
  • 2
    @matt I agree that trailing closure syntax could make it look marginally better (although I'm actually somewhat partial to using the `next:` parameter name in this case, as (at least to me) it adds clarity, and also makes Xcode align it nicely with the `state:` parameter). A `typealias` is a good idea if you're going to be invoking `sequence(state:next:)` multiple times with the same `State` type, although IMO seems somewhat redundant if it's only going to be used once. – Hamish Oct 23 '16 at 16:03
  • Oh, I agree it's something we shouldn't have to do. – matt Oct 23 '16 at 16:06
1

But even admitting that this is a bug, you can still make the code look a lot nicer instead of worrying about it (typed in the browser without testing, but something like this should work):

typealias MyTriple = (Bool, ClosedRangeIterator<Int>, ClosedRangeIterator<Int>)
let someTriple : MyTriple = (false, seq1.makeIterator(), seq2.makeIterator())
let combined = sequence(state: someTriple) { 
    (iters: inout MyTriple) -> Int? in
    iters.0 = !iters.0
    return iters.0 ? iters.1.next() : iters.2.next()
}
Hamish
  • 78,605
  • 19
  • 187
  • 280
matt
  • 515,959
  • 87
  • 875
  • 1,141