[swift-evolution] Universal Equatability, Hashability, and Comparability

Dave Abrahams dabrahams at apple.com
Wed Mar 9 15:19:41 CST 2016


on Tue Mar 08 2016, Jordan Rose <jordan_rose-AT-apple.com> wrote:

> Agreed. There are plenty of systems where == on classes means === by
> default, and then people forget to override == when they're making a
> class type that doesn't need identity.
>
> (Then again, should such a type be wrapped in a value type in Swift,
> to communicate that it doesn't use identity? 

Yes.

> But then that type will derive ==.)

Yes, but that's actually fine; its == will be a valid equivalence
relation.  If you want something else, define it.

> Making them derivable seems totally reasonable. Today just declaring
> Equatable or Hashable is enough to do that in the few places where we
> do derive conformances; we could either do that or invent a new
> "deriving" syntax.
>
> Jordan
>
>> On Mar 8, 2016, at 14:15, Austin Zheng via swift-evolution <swift-evolution at swift.org> wrote:
>> 
>> I would prefer Equatable and Hashable to remain opt-in, and for us
>> to add better support for automatic deriving of implementation.
>> 
>> For something like printing the representation of an object to a
>> string, there exists a "not wrong" mapping of every possible value
>> to a string. That is, if my FooStruct doesn't provide a custom
>> description, having the runtime convert it to something like
>> "(FooStruct instance)" is still a valid mapping. It might not be
>> useful, but it's not wrong.
>> 
>> I don't think the same applies for equatability. The universal
>> default behavior for equating two objects is either correct or
>> incorrect, and it's not possible to know beforehand which is
>> which. One of the wonderful things about the current Swift system is
>> that (modulo some exceptional cases) only things explicitly meant to
>> be equatable with each other are comparable. We avoid the
>> object-oriented pitfall in which 'equality' means two different
>> things - equality of value if you implemented an override properly;
>> a default 'equality of instance' otherwise (which might be right or
>> wrong). Of course, the same pitfall wouldn't necessarily apply in
>> our case, but the problem of having a 'default' == impl that allows
>> a developer to falsely assume their type is being properly compared
>> (or not think about it at all) would still be present.
>> 
>> Best,
>> Austin
>> 
>> 
>> 
>> On Tue, Mar 8, 2016 at 2:02 PM, Brian Pratt via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>
>> wrote:
>> Definitely a +1 on the basics. When you get inheritance involved, does that complicates things a little bit?
>> 
>> Let's say I have a subclass instance that has corresponding fields
>> with a superclass instance. Is it equal to said super-class instance
>> using just member-wise comparisons? Would that be problematic? In
>> Scala you'd often use a reference to an "equality contract" object
>> type in order to get "transitive" equality between subclasses and
>> superclasses, which definitely feels like a step backwards from the
>> current protocol-driven approach.
>> 
>> 
>> 
>> On Tue, Mar 8, 2016 at 2:54 PM, Joe Groff via swift-evolution
>> <swift-evolution at swift.org <mailto:swift-evolution at swift.org>>
>> wrote:
>> (starting a new thread by DaveA's request)
>> 
>> There's a definition of equality that makes sense as a default for nearly every type in our system:
>> 
>> - Basic types like IntNN, FloatNN, String, etc. have domain-defined equality,
>> - Structs and tuples can be considered equal if their corresponding fields are equal,
>> - Enums can be considered equal if they carry the same, equal payload,
>> - Class references can be considered equal if they refer to the same instance,
>> - Metatypes can be considered equal if they represent the same type, and
>> - Existentials can be considered equal if they carry equal values of the same dynamic type.
>> 
>> and similarly, reasonable hash code implementations could be
>> synthesized by applying a standard hash combine operation over the
>> components, and a default ordering could be assigned to values of
>> every type. I think it's worth considering whether Equatable,
>> Hashable, and/or Comparable, instead of being explicit protocols,
>> should become universal behavior like 'print', with customization
>> points to override the default behavior. If Equatable and Hashable
>> behavior were universal, that would solve many of the common
>> problems people currently have trying to work with heterogeneous
>> containers. In object-oriented frameworks, including Cocoa, Java,
>> and .NET, it is common for the root (NS)Object class to provide
>> default equality and hashing operations. There are of course some
>> tradeoffs:
>> 
>> - Universal behavior would require us to either generate code for
>> '==', 'hashValue', and/or '<' for every type, or provide sufficient
>> reflection info for a common runtime implementation to do it. The
>> reflection-based approach may be reasonable for print(), since
>> dumping reflection info only reduces the quality of the default
>> logging behavior, but '==' and 'hashValue' are more essential to
>> proper behavior, so relying on reflection might be too slow, and
>> would be brittle when we introduce the ability to drop reflection
>> info.
>> - Type safety with '==' is important to prevent accidental '1 ==
>> "1"' type comparsions, and a fully generic 'func ==<T>(x: T, y: T)
>> -> Bool' could potentially allow those sorts of mixed-type
>> comparisons by accident. Language rules that constrained when
>> generic parameters can be resolved to supertypes might help here.
>> - Function types in Swift do not provide a ready equality
>> operation. We could provide a default implementation that always
>> returns 'false', perhaps.
>> - A Comparable ordering can be dreamt up for many types, but it's
>> not always a stable ordering, or a desired one. Many people have
>> complained that 'nil < .Some(1)' works for optionals, for instance,
>> ordering 'nil' below Some values. We could use pointer identity to
>> order class instances and types, but this wouldn't be a stable
>> ordering across process runs. That might be good enough for ordered
>> collections like search trees, but is weaker than what many people
>> expect '<' to do.
>> 
>> It's my feeling that Equatable and Hashable would make a lot of
>> sense as universal operations; I'm not so sure about Comparable.
>> 
>> -Joe
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <https://lists.swift.org/mailman/listinfo/swift-evolution>
>> 
>> 
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>

-- 
-Dave


More information about the swift-evolution mailing list