[swift-evolution] [Draft] Rename Sequence.elementsEqual

Xiaodi Wu xiaodi.wu at gmail.com
Thu Oct 12 18:23:53 CDT 2017

Rename Sequence.elementsEqual

   - Proposal: SE-NNNN
   - Authors: Xiaodi Wu <https://github.com/xwu>
   - Review Manager: TBD
   - Status: *Awaiting review*


The current behavior of Sequence.elementsEqual is potentially confusing to
users given its name. Having surveyed the alternative solutions to this
problem, it is proposed that the method be renamed to

As outlined by Ole Begemann
<https://twitter.com/olebegemann/status/916291785185529857>, use of
Sequence.elementsEqual(_:) can lead to surprising results if the sequences
compared are unordered:

var set1: Set<Int> = Set(1...5)var set2: Set<Int> = Set((1...5).reversed())

set1 == set2 // trueset1.elementsEqual(set2) // false

This result does reflect the *intended and documented* behavior of the
elementsEqual(_:) method, which performs a *lexicographical* elementwise
comparison. That is, the method first compares set1.first to set2.first,
then (if the two elements compare equal) compares the next element stored
internally in set1 to the next element stored internally in set2, and so on.

In almost all circumstances where a set is compared to another set, or a
dictionary is compared to another dictionary, users should use == instead
of elementsEqual(_:).

The proposed solution is the result of an iterative process of reasoning,
presented here:

The first and most obvious solution is to remove the elementsEqual(_:)
method altogether in favor of ==. This prevents its misuse. However,
because elementsEqual(_:) is a generic method on Sequence, we can use it to
compare an instance of UnsafeBufferPointer<Int> to an instance of [Int].
This is a useful and non-redundant feature which would be eliminated if the
method is removed altogether.

A second solution <https://github.com/apple/swift/pull/12318> is to create
overloads that forbid the use of the elementsEqual(_:) method specifically
in non-generic code. This would prevent misuse in non-generic code;
however, it would also forbid legitimate mixed-type comparisons in
non-generic code while failing to prevent misuse in generic code. The
solution also creates a difference in the behavior of generic and
non-generic code calling the same method, which is potentially confusing,
without solving the problem completely.

A third solution is to dramatically overhaul the protocol hierarchy for
Swift sequences and collections so that unordered collections no longer
have members such as first and elementsEqual(_:). However, this would be a
colossal and source-breaking undertaking, and it is unlikely to be
satisfactory in addressing all the axes of differences among sequence and
collection types:

   - Finite versus infinite
   - Single-pass versus multi-pass
   - Ordered versus unordered
   - Lazy versus eager
   - Forward/bidirectional/random-access

A fourth solution is proposed here. It is predicated on the following

*Another method similar to elementsEqual(_:) already exists on Sequence
named lexicographicallyPrecedes(_:). Like first, elementsEqual(_:),
drop(while:), and others, it relies on the internal order of elements in a
manner that is not completely suitable for an unordered collection.
However, like first and unlike elementsEqual(_:), this fact is called out
in the name of the method; unsurprisingly, like first and unlike
elementsEqual(_:), there is no evidence that lexicographicallyPrecedes(_:)
has been a pitfall for users.*

This observation suggests that a major reason for confusion over
elementsEqual(_:) stems from its name. So, *it is proposed that
elementsEqual(_:) should be renamed to lexicographicallyEquals(_:)*. The
function will remain somewhat of a poor fit for unordered collections, but
no more so than many other methods that cannot trivially be removed from
the API of unordered collections (as discussed above). The key is that,
with such a renaming, the behavior of this method will no longer be

extension Sequence where Element : Equatable {
  @available(*, deprecated, message: "Use '==' if possible to compare
two sequences of the same type, or use 'lexicographicallyEquals' to
compare two ordered sequences.")
  public func elementsEqual<Other : Sequence>(
    _ other: Other
  ) -> Bool where Other.Element == Element {
    return lexicographicallyEquals(other)

  public func lexicographicallyEquals<Other : Sequence>(
    _ other: Other
  ) -> Bool where Other.Element == Element {
    // The body of this method is unchanged.    var iter1 = self.makeIterator()
    var iter2 = other.makeIterator()
    while true {
      switch (iter1.next(), iter2.next()) {
      case let (e1?, e2?):
        if e1 != e2 { return false }
      case (_?, nil), (nil, _?):
        return false
      case (nil, nil):
        return true

A parallel change will be made with respect to elementsEqual(_:by:); that
is, it will be deprecated in favor of lexicographicallyEquals(_:by:).

Existing code that uses elementsEqual will gain a deprecation warning.
on ABI stability

on API resilience

This proposal adds new methods to the public API of Sequence and conforming

It is to be noted that lexicographicallyPrecedes(_:by:) and
elementsEqual(_:by:) are essentially the same method, since both perform a
lexicographical comparison using a custom predicate. However, there is not
a good unifying name. (lexicographicallyCompares(to:by:) reads poorly.)
Moreover, the predicate supplied is intended to have very different
semantics, and maintaining two distinct methods may be a superior fit with
the typical user's mental model of the intended behavior and may also be
clearer to readers of the code. Therefore, this proposal does not seek to
unify the two methods; instead, elementsEqual(_:by:) will be renamed
lexicographicallyEquals(_:by:) as detailed above.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20171012/a4f7d6f0/attachment.html>

More information about the swift-evolution mailing list