Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ambiguous use of functions (Swift 4)

I've been trying to figure out why the following code is ambiguous...

public func product <T1: Sequence, T2: Sequence> (_ sequence1: T1, _ sequence2: T2) ->
    AnySequence<(T1.Element, T2.Element)> {
  return AnySequence(
    sequence1.flatMap { element1 in
      sequence2.map { element2 in
        (element1, element2)
      }
    }
  )
}

public func product <T1: LazySequenceProtocol, T2: LazySequenceProtocol> (_ sequence1: T1, _ sequence2: T2) ->
    LazySequence<AnySequence<(T1.Element, T2.Element)>> {
  return AnySequence(
    sequence1.flatMap { element1 in
      sequence2.map { element2 in
        (element1, element2)
      }
    }
  ).lazy
}

...when I call it with two lazy sequences and a call to makeIteratorEXAMPLE.

_ = product([1, 2].lazy, [3, 4].lazy).makeIterator()

Yet, the following code doesn't have this ambiguity...

public struct Product2Sequence <T1: Sequence, T2: Sequence>: Sequence {
  public typealias Element = (T1.Element, T2.Element)
  public typealias Iterator = AnyIterator<Element>

  private let iterator: Iterator

  internal init (_ sequence1: T1, _ sequence2: T2) {
    self.iterator = AnyIterator(
      sequence1.flatMap { element1 in
        sequence2.map { element2 in
          (element1, element2)
        }
      }.makeIterator()
    )
  }

  public func makeIterator () -> Iterator {
    return self.iterator
  }
}

public struct LazyProduct2Sequence <T1: LazySequenceProtocol, T2: LazySequenceProtocol>: LazySequenceProtocol {
  public typealias Element = (T1.Element, T2.Element)
  public typealias Iterator = AnyIterator<Element>

  private let iterator: Iterator

  internal init (_ sequence1: T1, _ sequence2: T2) {
    self.iterator = AnyIterator(
      sequence1.flatMap { element1 in
        sequence2.map { element2 in
          (element1, element2)
        }
      }.makeIterator()
    )
  }

  public func makeIterator () -> Iterator {
    return self.iterator
  }
}

public func product <T1: Sequence, T2: Sequence> (_ sequence1: T1, _ sequence2: T2) -> Product2Sequence<T1, T2> {
  return Product2Sequence(sequence1, sequence2)
}

public func product <T1: LazySequenceProtocol, T2: LazySequenceProtocol> (_ sequence1: T1, _ sequence2: T2) ->
    LazyProduct2Sequence<T1, T2> {
  return LazyProduct2Sequence(sequence1, sequence2)
}

...when I call it with two lazy sequences and a call to makeIteratorEXAMPLE.

_ = product([1, 2].lazy, [3, 4].lazy).makeIterator()

My reasoning is that a lazy sequence conforms both to LazySequenceProtocol and Sequence so the type system doesn't know which product to choose. But by that definition the second version should also not work.

I'm using Swift 4.0.

What makes the second version work?

like image 537
Dennis Vennink Avatar asked Aug 15 '17 10:08

Dennis Vennink


1 Answers

This is a bug that was resolved with 4.1 (see https://bugs.swift.org/browse/SR-4509). There used to be no way for the type solver to choose between Sequence and LazySequenceProtocol, as the former obviously conforms to the latter.

I can't say for sure why your second version does work with 4.0 and have no evidence to support my assumptions, but I would guess it has something to do with the type solver being able to get more information from more concrete return types.

Anyway, LazySequenceProtocol is now preferred so as to preserve laziness as long as possible. Your code works as expected with Swift 4.1.

like image 160
Alvae Avatar answered Oct 06 '22 00:10

Alvae