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?

Terms of Service

Privacy Policy

Cookie Policy