This comparison shouldn't need tuple conformance to Equatable, right?

I'm using Xcode version 11.4.1 (11E503a) on a Catalina system, with a playground.

extension Sequence {

    /// Returns the first corresponding pair of elements from this sequence and
    /// the given sequence that are not equivalent, using the given predicate as
    /// the equivalence test.
    public func firstDelta<S: Sequence>(
        against other: S,
        by areEquivalent: (Element, S.Element) throws -> Bool
    ) rethrows -> (Element?, S.Element?) {
        var iterator = makeIterator(), otherIterator = other.makeIterator()
        while true {
            let first = iterator.next(), second = otherIterator.next()
            if let f = first, let s = second, try areEquivalent(f, s) {
                continue
            } else {
                return (first, second)
            }
        }
    }

    func elementsEqual2<OtherSequence: Sequence>(
        _ other: OtherSequence,
        by areEquivalent: (Element, OtherSequence.Element) throws -> Bool
    ) rethrows -> Bool {
        return try firstDelta(against: other, by: areEquivalent) == (nil, nil)  // HERE
    }

}

The error is on the "==" of the indicated line:

Type '(Self.Element?, OtherSequence.Element?)' cannot conform to 'Equatable'; only struct/enum/class types can conform to protocols

I know we're working on this, but it shouldn't matter here, since the already provided top-level functions should cover it, and we're not checking for conformance.

Wouldn't you need a constraint that Element and OtherSequence.Element also conform to Equatable to be able to choose the == <T: Equatable, U: Equatable> (lhs: (T, U), rhs: (T, U)) variant?

It works for me if I add those constraints.

1 Like

Adapting your solution worked, but that isn't the real problem.

I forgot that any Optional can be equality-compared to nil, even if the Wrapped type isn't Equatable. There is a custom == operator for nil comparisons. But a custom operator outside of the Equatable network isn't transitive; I guess the current way of tuple per-member equality check uses Equatable conformance, and not any existing == that is accessible.

So I had to separate the members before comparing:

func elementsEqual2<OtherSequence: Sequence>(
    _ other: OtherSequence,
    by areEquivalent: (Element, OtherSequence.Element) throws -> Bool
) rethrows -> Bool {
    let rawResult = try firstDelta(against: other, by: areEquivalent)
    return rawResult.0 == nil && rawResult.1 == nil
}

TIL about _OptionalNilComparisonType!

1 Like

That’s right, there are specific == (and !=) overloads for tuples with up to six Equatable members. If you want to be able to compare tuples directly to (nil, nil, ...), you’d need to define your own overloads:

public func == <A, B>(lhs: (A?, B?), rhs: (_OptionalNilComparisonType, _OptionalNilComparisonType)) -> Bool {
    return lhs.0 == rhs.0 && lhs.1 == rhs.1
}

// Plus overloads for reversed lhs and rhs, !=, and longer tuples

But it might be simpler to just compare the elements to nil one-by-one in elementsEqual2:

let delta = try firstDelta(against: other, by: areEquivalent)
return delta.0 == nil && delta.1 == nil

I think you're making this too complex by trying to make (nil, nil) actually mean the same as a wrapping nil. Instead, how about this?

extension Sequence {
  /// Returns the first corresponding pair of elements from this sequence and
  /// the given sequence that are not equivalent, using the given predicate as
  /// the equivalence test.
  public func firstDelta<S: Sequence>(
    against other: S,
    by areEquivalent: (Element, S.Element) throws -> Bool
  ) rethrows -> (Element?, S.Element?)? {
    try AnySequence( zip: (self, other) ).first {
      try Optional(optionals: $0).map(areEquivalent).map(!)
      ?? true
    }
  }

  func elementsEqual2<OtherSequence: Sequence>(
    _ other: OtherSequence,
    by areEquivalent: (Element, OtherSequence.Element) throws -> Bool
  ) rethrows -> Bool {
    try firstDelta(against: other, by: areEquivalent) == nil
  }
}
public extension AnySequence {
  /// Like `zip`, but with nil elements after shorter sequences are exhausted.
  init<Sequence0: Sequence, Sequence1: Sequence>(
    zip zipped: (Sequence0, Sequence1)
  ) where Element == (Sequence0.Element?, Sequence1.Element?) {
    self.init(
      sequence(
        state: ( zipped.0.makeIterator(), zipped.1.makeIterator() )
      ) { iterators in
        Optional(
          ( iterators.0.next(), iterators.1.next() ),
          nilWhen: { $0 == nil && $1 == nil }
        )
      }
    )
  }
}
public extension Optional {
  /// Wraps a value in an optional, based on a condition.
  /// - Parameters:
  ///   - wrapped: A non-optional value.
  ///   - getIsNil: The condition that will result in `nil`.
  init(
    _ wrapped: Wrapped,
    nilWhen getIsNil: (Wrapped) throws -> Bool
  ) rethrows {
    self = try getIsNil(wrapped) ? nil : wrapped
  }

  /// Exchange two optionals for a single optional tuple.
  /// - Returns: `nil` if either tuple element is `nil`.
  init<Wrapped0, Wrapped1>( optionals: (Wrapped0?, Wrapped1?) )
  where Wrapped == (Wrapped0, Wrapped1) {
    switch optionals {
    case let (wrapped0?, wrapped1?):
      self = (wrapped0, wrapped1)
    default:
      self = nil
    }
  }
}

I don't want to make that equivalence; I just wanted to compare both members to nil at once. It it's not worth it, so I just did:

I have two Optionals within a non-Optional tuple because the four states have distinct user-level meanings. Neither being nil means each sequence has at least one element after a common prefix. Only the first as nil means that the first sequence is a prefix of the second. Vice-versa goes when only the second is nil. The sequences are identical when both returns are nil. What is your fifth state (making the entire tuple nil) supposed to mean?