Title: Equality, Identity, and Comparisons
Start Date: June 24 2020
Status: Draft
Unify ==
and ===
, introduce sane alternatives for classes that need to use
behaviour similar to the old versions.
Hack has two main comparison operators ==
and ===
. They're individually
comprised of behaviours both good and bad, and combine to a muddied, confusing,
and overall broken experience. ==
does coercing value equality, including on
objects, and compares arrays (sometimes unorderedly). ===
does pointer equality
checks on objects (including collections), and breaks some invariants for
reactive and pure code.
We'd like to simplify and correct the mistakes of the previous design
===
- Objects: pointer equality (note that closures are instances of different objects)
- arrays: compares all elements (keys and values) using
===
, requiring identical internal ordering. - values: value equality
- Hack Collections: pointer equality (they are objects)
==
- Objects: compares all properties using
==
- note that closures still don’t compare equal because they’re instances of different objects
- arrays: compares all elements (keys and values) using
===
for keys and==
for values, ignoring order fordicts
/darrays
/keysets
- values: value equality following complex PHP coercion rules.
- Hack Collections: compares all elements using
==
, ignoring order forMap
/Set
- If comparing different types will attempt to coerce one side to the type of the other before comparing
- Objects: compares all properties using
<=
,>=
: Use==
under the hood. No===
variant.- Sorts and switch statements implicitly use the == behaviour under the hood
- To test object pointer equality, use builtin
is_same_obj($a, $b)
that does a pointer check- Potentially would require a marker interface such as
IIdentityTestable
(name to be bikeshed). - In that case, whether
is_same_obj
is free standing or works as$a→isSame($b)
to be bikeshed- In the method case, this is non-overrideable
- Potentially would require a marker interface such as
- To get object structural equality require explicit opt-in via an interface such as
IEquatable
- To be bikeshed: does this allow using
==
or does it do something like$a→eq($b)
- In the method case, this is non-overrideable
- Would require all properties to be either values or also
IEquatable
- Note that this would probably do an identity test first as an optimization
- This also would require reflexivity for the optimization to be sound.
- If we can’t compare Collections and Arrays, how do props of those types work? Do we allow it implicitly in this case?
- To be bikeshed: does this allow using
- For the previous two, if we go the object method route of comparison, absolutely cannot allow overriding implementations.
==
works as follows- never coerces the arguments
- Objects: see 1 and 2.
- If
IEquatable
means can use==
, do we return false or throw when not present? - What happens when objects have different types?
- If
- Bikeshed more: Cannot compare arrays/collections. Use HSL or Collections methods
- Values: work the same
- Closures are not
IEquatable
, and pointer equality on two of them is always false (or throws?).- This will cause issues with reflexivity.
- bikeshed:
resource
andNaN
? Other edge cases?- Note that NaN is already weird. Currently
NaN !== Nan
butvec[NAN] === vec[NAN]
- Note that NaN is already weird. Currently
===
doesn’t exist<=
,>=
, etc inherit the changes to ==- Are
IEquatable
objects comparable this way? I expect not. - Do we allow them on Containers?
- Are
- Having an object-arg to a memoized
pure
/rx
function requires them to beIEquatable
- Sorts and switch statements use the new sane == under the hood
- Will likely need a builtin coercing_eq_yikity_yikes() that does the old == behaviour for migration purposes
N/A
Mostly still open questions above or below. Did I miss any?
What the typing rules of == under this proposal? Is it still a function that takes two values of type mixed?
TODO once we get more consensus about the open questions above
TODO
It's a ton of work. Probably not HAM level, but definitely a major project from the HF perspectivce
TODO
Note rust's ord, eq, partial ord, and partial eq. Rust (which took these ideas from Haskell) only allows you to compare values that implement these traits (typeclasses in Haskell).
How do function pointers work? And are you guaranteed to get the same pointer for two different calls
Does structural equality on well-formed IEquatable classes always work?
class ListNode implements IEquatable {
public function __construct(
public int $val,
public ?ListNode $prev = null,
public ?ListNode $next = null,
) {}
}
function compare_nodes(): void {
// Build a linked list [1, 2].
$n = new ListNode(1);
$n2 = new ListNode(2, $n);
$n->next = $n2;
// What value does $b have?
$b = $n == $n2;
}
The obvious recursive function for structural equality would loop infinitely on $n == $n2.
I think this is mostly N/A? Unless we want to reuse === for something?