Merge ordered and unordered comparators (#5845)

Merge _ordered_ and _unordered_ comparators into a single one.

# Important Notes
Comparator is now required to have only `compare` method:
```
type Comparator
comapre : T -> T -> (Ordering|Nothing)
hash : T -> Integer
```
This commit is contained in:
Pavel Marek 2023-03-11 06:43:22 +01:00 committed by GitHub
parent a7f1ba96a9
commit 5f7a4a5a39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 145 additions and 318 deletions

View File

@ -614,6 +614,7 @@
- [Update to GraalVM 22.3.1][5602]
- [Cache library bindings to optimize import/export resolution][5700]
- [Comparators support partial ordering][5778]
- [Merge ordered and unordered comparators][5845]
- [Use SHA-1 for calculating hashes of modules' IR and bindings][5791]
[3227]: https://github.com/enso-org/enso/pull/3227
@ -713,6 +714,7 @@
[5602]: https://github.com/enso-org/enso/pull/5602
[5700]: https://github.com/enso-org/enso/pull/5700
[5778]: https://github.com/enso-org/enso/pull/5778
[5845]: https://github.com/enso-org/enso/pull/5845
[5791]: https://github.com/enso-org/enso/pull/5791
# Enso 2.0.0-alpha.18 (2021-10-12)

View File

@ -108,26 +108,19 @@ type Any
== self that =
# If there is No_Such_Conversion, then `self` and `that` are probably
# host or polyglot values, so we just compare them with the default comparator.
eq_self = Panic.catch No_Such_Conversion (Comparable.from self) _-> Default_Unordered_Comparator
eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Unordered_Comparator
case Meta.is_same_object eq_self Incomparable of
True -> False
False ->
similar_type = Meta.is_same_object eq_self eq_that
case similar_type of
False -> False
True ->
case eq_self.is_ordered of
True ->
res = eq_self.compare self that
case res of
Ordering.Equal -> True
_ -> False
False ->
res = eq_self.equals self that
case res of
Nothing -> False
_ -> res
eq_self = Panic.catch No_Such_Conversion (Comparable.from self) _-> Default_Comparator
eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Comparator
similar_type = Meta.is_same_object eq_self eq_that
case similar_type of
False -> False
True ->
case Meta.is_same_object eq_self Default_Comparator of
# Shortcut for objects with Default_Comparator, because of the performance.
True -> Comparable.equals_builtin self that
False ->
case eq_self.compare self that of
Ordering.Equal -> True
_ -> False
## ALIAS Inequality
@ -159,8 +152,10 @@ type Any
Arguments:
- that: The value to compare `self` against.
To have `>` properly defined, a type must have an associated ordered comparator.
See `Ordering.enso` for information how comparators work.
To be comparable, a custom object must have an associated comparator
which will return `Ordering.Less/Greater` for unequal values. Otherwise,
this will raise `Incomparable_Values` error. See `Ordering.enso` for
information how comparators work.
> Example
Checking if the variable `a` is greater than `147`.
@ -172,7 +167,7 @@ type Any
a > 147
> : Any -> Boolean ! Incomparable_Values
> self that =
assert_ordered_comparators self that <|
assert_same_comparators self that <|
case (Comparable.from self).compare self that of
Ordering.Greater -> True
Nothing -> Error.throw (Incomparable_Values.Error self that)
@ -185,8 +180,6 @@ type Any
Arguments:
- that: The value to compare `self` against.
To have `>=` defined, a type must define both `>` and `==`.
! Implementing Greater Than or Equal
While it is often possible to implement a more efficient version of this
operation for complex types, care must be taken to ensure that your
@ -203,12 +196,12 @@ type Any
a >= 147
>= : Any -> Boolean ! Incomparable_Values
>= self that =
assert_ordered_comparators self that <|
assert_same_comparators self that <|
case (Comparable.from self).compare self that of
Ordering.Less -> False
Ordering.Equal -> True
Ordering.Greater -> True
Nothing -> Error.throw (Incomparable_Values.Error self that)
_ -> False
## ALIAS Less Than
@ -217,8 +210,10 @@ type Any
Arguments:
- that: The value to compare `self` against.
To have `<` properly defined, a type must have an associated ordered comparator.
See `Ordering.enso` for information how comparators work.
To be comparable, a custom object must have an associated comparator
which will return `Ordering.Less/Greater` for unequal values. Otherwise,
this will raise `Incomparable_Values` error. See `Ordering.enso` for
information how comparators work.
> Example
Checking if the variable `a` is less than `147`.
@ -230,7 +225,7 @@ type Any
a < 147
< : Any -> Boolean ! Incomparable_Values
< self that =
assert_ordered_comparators self that <|
assert_same_comparators self that <|
case (Comparable.from self).compare self that of
Ordering.Less -> True
Nothing -> Error.throw (Incomparable_Values.Error self that)
@ -261,12 +256,12 @@ type Any
a < 147
<= : Any -> Boolean ! Incomparable_Values
<= self that =
assert_ordered_comparators self that <|
assert_same_comparators self that <|
case (Comparable.from self).compare self that of
Ordering.Less -> True
Ordering.Equal -> True
Ordering.Greater -> False
Ordering.Less -> True
Nothing -> Error.throw (Incomparable_Values.Error self that)
_ -> False
## Checks if the type is an instance of `Nothing`.
@ -456,13 +451,12 @@ type Any
## PRIVATE
Checks if both comparators of the given objects are both of same type and ordered.
If they are not of same type, a `Type_Error` is thrown.
If the comparators are either `Incomparable`, or unordered, `False` is returned.
assert_ordered_comparators : Any -> (Any -> Any) -> Any ! (Type_Error | Incomparable_Values)
assert_ordered_comparators this that ~action =
Checks if the comparators for the given objects are both of the same type. If so,
proceeds with the given action, and if not, throws `Type_Error`.
assert_same_comparators : Any -> Any -> (Any -> Any) -> Any ! Type_Error
assert_same_comparators this that ~action =
comp_this = Comparable.from this
comp_that = Comparable.from that
if (Meta.is_same_object comp_this comp_that).not then Error.throw (Type_Error.Error (Meta.type_of comp_this) comp_that "comp_that") else
if Meta.is_same_object comp_this Incomparable || comp_this.is_ordered.not then Error.throw (Incomparable_Values.Error this that) else
action
case Meta.is_same_object comp_this comp_that of
True -> action
False -> Error.throw (Type_Error.Error (Meta.type_of comp_this) comp_that "comp_that")

View File

@ -1,7 +1,6 @@
import project.Any.Any
import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Nothing.Nothing
from project.Data.Boolean.Boolean import True, False
@ -90,5 +89,3 @@ type Boolean
if_then : Any -> Any | Nothing
if_then self ~on_true = @Builtin_Method "Boolean.if_then"
Comparable.from (_:Boolean) = Default_Ordered_Comparator

View File

@ -185,15 +185,13 @@ type JS_Object
## PRIVATE
type JS_Object_Comparator
is_ordered : Boolean
is_ordered = False
equals : JS_Object -> JS_Object -> Boolean
equals obj1 obj2 =
compare : JS_Object -> JS_Object -> (Ordering|Nothing)
compare obj1 obj2 =
obj1_keys = obj1.field_names
obj2_keys = obj2.field_names
obj1_keys.length == obj2_keys.length && obj1_keys.all key->
same_values = obj1_keys.length == obj2_keys.length && obj1_keys.all key->
(obj1.get key == obj2.at key).catch No_Such_Key _->False
if same_values then Ordering.Equal else Nothing
hash : JS_Object -> Integer
hash obj =

View File

@ -1,7 +1,3 @@
import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable
import project.Data.Ordering.Incomparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text
import project.Data.Locale.Locale
import project.Error.Common.Arithmetic_Error
@ -941,8 +937,6 @@ type Integer
parse_builtin text radix = @Builtin_Method "Integer.parse"
Comparable.from (_:Number) = Default_Ordered_Comparator
## UNSTABLE
A syntax error when parsing a double.

View File

@ -23,42 +23,29 @@ from project.Data.Boolean import all
Should there be a need to redefine the default implementation, here is a way:
Define conversion function `Comparable.from` for your `type` and return pointer to
another `type` that satisfies either of the following two definitions:
another `type` that satisfies the following definition:
```
type Ordered_Comparator T
is_ordered = True
type Comparator T
compare : T -> T -> (Ordering|Nothing)
hash : T -> Integer
type Unordered_Comparator T
is_ordered = False
equals : T -> T -> (Boolean|Nothing)
hash : T -> Integer
```
Or `Incomparable` in case that the type `T` should not be compared at all.
Every type must provide exactly one comparator, i.e., the method of form
`Comparable.from (_:My_Type)` must return the same comparator type for every
value. Note that there is an implicit `Default_Comparator` assigned for every
type by default.
Every type must provide exactly one comparator, i.e., any method of form
`Comparable.from (_:My_Type)` must always return the same comparator type.
Note that if there is an implicit `Default_Unordered_Comparator` assigned
for every type by default.
A comparator has to implement `compare` and `hash` methods. `compare x y` method
returns `Ordering.Less` if x is ordered before y, `Ordering.Equal` if x is equal
to y, `Ordering.Greater` if x is ordered after y, and `Nothing` if x and y are
not equal, and their relative ordering cannot be determined.
Note that there has to be `is_ordered` method defined which returns a Boolean
indicating that the comparator is ordered. This is currently needed as there is
no way to define interfaces in Enso.
The signature of the `compare` method is designed so that it enables comparators
to provide a _partial ordering_ for a specific type.
An _unordered comparator_ has to implement both `equals` and `hash` to define
a _partial_ custom equality. By _partial_, we mean that every instance of the type
has to be either equal, not equal, or undefined, which is represented by the type signature
of `equals` - when a `Boolean` is returned, the two instances are either equal
or not equal, when `Nothing` is returned, the instances are incomparable.
An _ordered comparator_ has `compare` method instead of `equals` method, that is
expected to return `Ordering` signaling that an instance of the type is either
less than, equal to, or greater than the other instance, or `Nothing` signaling
that the two instances are incomparable. This relation may also be _partial_, meaning
that some of the instances may be incomparable.
A hash code must be provided for all the objects, therefore, the type signature
of `hash` method does not allow `Nothing` as a return value.
The runtime expects the following semantics for all the comparators:
- Hash consistency:
@ -85,12 +72,10 @@ from project.Data.Boolean import all
Value x y
type UPair_Comparator
is_ordered = False
equals pair1 pair2 =
if pair1.x == pair2.x && pair1.y == pair2.y then True else
if pair1.x == pair2.y && pair1.y == pair2.x then True else
False
compare pair1 pair2 =
if pair1.x == pair2.x && pair1.y == pair2.y then Ordering.Equal else
if pair1.x == pair2.y && pair1.y == pair2.x then Ordering.Equal else
Nothing
hash upair =
x_comp = Comparable.from upair.x
@ -111,7 +96,6 @@ from project.Data.Boolean import all
Comparable.from (_:Rational) = Rational_Ordering
type Rational_Ordering
is_ordered = True
compare self r1 r2 =
v1 = r1.numerator * r2.denominator
v2 = r2.numerator * r1.denominator
@ -124,19 +108,12 @@ from project.Data.Boolean import all
By defining the `Rational_Ordering` and making it available via
`Comparable.from (_:Rational)` method, all parts of the Enso system will use
the custom comparator whenever equality or hash code is needed.
The equality check of two objects:
- verifies both objects share the same type of `comparator`
- consults their `compare`, or `equals` method, based on whether the comparator is
ordered or unordered.
- if the result is `Ordering.Equal` the system also checks that both instances have the same `hash`
- the `hash` code check may be done only from time to time to speed things up
@Builtin_Type
type Comparable
## PRIVATE
Called as a callback from internal engine code for an atom with a custom
comparator. It is assumed that the given atom has a custom comparator, that is
a comparator different than `Default_Unordered_Comparator`.
a comparator different than `Default_Comparator`.
hash_callback : Atom -> Integer
hash_callback atom = (Comparable.from atom).hash atom
@ -146,52 +123,30 @@ type Comparable
has_custom_comparator : Atom -> Boolean
has_custom_comparator atom =
comp = Comparable.from atom
(comp.is_a Default_Unordered_Comparator).not && (comp.is_a Default_Ordered_Comparator).not
(comp.is_a Default_Comparator).not
## Singleton denoting that values of certain type are not comparable.
type Incomparable
Singleton
## Default implementation of unordered comparator.
## Default implementation of a _comparator_.
@Builtin_Type
type Default_Unordered_Comparator
is_ordered = False
equals : Any -> Any -> (Boolean|Nothing)
equals x y = Comparable.equals_builtin x y
hash : Any -> Integer
hash object = Comparable.hash_builtin object
## Default implementation of an ordered _comparator_. Handles only primitive types,
does not handle atoms, or vectors. Any type that requires an ordering, must
define its own ordered comparator.
@Builtin_Type
type Default_Ordered_Comparator
is_ordered = True
## Handles only primitive types, not atoms or vectors.
type Default_Comparator
compare : Any -> Any -> (Ordering|Nothing)
compare x y =
case Comparable.less_than_builtin x y of
Nothing -> Nothing
True -> Ordering.Less
case Comparable.equals_builtin x y of
True -> Ordering.Equal
False ->
case Comparable.equals_builtin x y of
case Comparable.less_than_builtin x y of
Nothing -> Nothing
True -> Ordering.Equal
True -> Ordering.Less
False ->
case Comparable.less_than_builtin y x of
Nothing -> Nothing
True -> Ordering.Greater
False -> Panic.throw (Illegal_State.Error "Unreachable")
False -> Nothing
hash : Number -> Integer
hash x = Comparable.hash_builtin x
Comparable.from (_:Any) = Default_Unordered_Comparator
Comparable.from (_:Any) = Default_Comparator
## Types representing the ordering of values.
@ -250,7 +205,6 @@ type Ordering
if sign > 0 then Ordering.Greater else Ordering.Less
type Ordering_Comparator
is_ordered = True
compare x y = (Comparable.from x.to_sign).compare x.to_sign y.to_sign
hash x = x.to_sign

View File

@ -5,7 +5,6 @@ import project.Error.Common.Type_Error
import project.Error.Error
import project.Meta
from project.Data.Ordering import all
from project.Data.Boolean import Boolean, True, False
polyglot java import org.enso.base.Text_Utils
@ -118,5 +117,3 @@ type Text
Conversion to Text that overrides the default `to_text` behavior.
to_text : Text
to_text self = self
Comparable.from (_:Text) = Default_Ordered_Comparator

View File

@ -4,7 +4,6 @@ import project.Data.Locale.Locale
import project.Data.Numbers.Integer
import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text
import project.Data.Time.Date_Period.Date_Period
import project.Data.Time.Date_Time.Date_Time
@ -627,6 +626,3 @@ is_weekend date =
## PRIVATE
fits_in_range start end date =
(start <= date) && (date < end)
Comparable.from (_:Date) = Default_Ordered_Comparator

View File

@ -4,7 +4,6 @@ import project.Data.Locale.Locale
import project.Data.Numbers.Integer
import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text
import project.Data.Time.Date.Date
import project.Data.Time.Date_Period.Date_Period
@ -687,5 +686,3 @@ type Date_Time
format : Text -> Text
format self pattern = @Builtin_Method "Date_Time.format"
Comparable.from (_:Date_Time) = Default_Ordered_Comparator

View File

@ -3,7 +3,6 @@ import project.Data.Json.JS_Object
import project.Data.Numbers.Decimal
import project.Data.Numbers.Integer
import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Pair.Pair
import project.Data.Time.Date_Time.Date_Time
import project.Data.Time.Period.Period
@ -294,5 +293,3 @@ type Duration
example_is_empty = Duration.zero.is_empty
is_empty : Boolean
is_empty self = self.to_vector . all (==0)
Comparable.from (_:Duration) = Default_Ordered_Comparator

View File

@ -3,7 +3,6 @@ import project.Data.Numbers.Integer
import project.Data.Time.Date.Date
import project.Data.Time.Duration.Duration
import project.Data.Ordering.Comparable
import project.Data.Ordering.Incomparable
import project.Data.Text.Text
import project.Error.Error
import project.Error.Illegal_Argument.Illegal_Argument

View File

@ -3,7 +3,6 @@ import project.Data.Json.JS_Object
import project.Data.Locale.Locale
import project.Data.Numbers.Integer
import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text
import project.Data.Time.Date.Date
import project.Data.Time.Date_Time.Date_Time
@ -356,5 +355,3 @@ type Time_Of_Day
example_format = Time_Of_Day.new 16 21 10 . format "'hour:'h"
format : Text -> Text
format self pattern = @Builtin_Method "Time_Of_Day.format"
Comparable.from (_:Time_Of_Day) = Default_Ordered_Comparator

View File

@ -77,8 +77,7 @@ import project.Data.Noise
import project.Data.Ordering.Natural_Order
import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable
import project.Data.Ordering.Incomparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Ordering.Default_Comparator
import project.Data.Ordering.Sort_Direction.Sort_Direction
import project.Data.Pair.Pair
import project.Data.Range.Range
@ -131,8 +130,7 @@ export project.Data.Maybe.Maybe
export project.Data.Ordering.Natural_Order
export project.Data.Ordering.Ordering
export project.Data.Ordering.Comparable
export project.Data.Ordering.Incomparable
export project.Data.Ordering.Default_Ordered_Comparator
export project.Data.Ordering.Default_Comparator
export project.Data.Ordering.Sort_Direction.Sort_Direction
export project.Data.Pair.Pair
export project.Data.Range.Range

View File

@ -24,7 +24,6 @@ type Bits
Bits.Bits_64 -> 64
type Bits_Comparator
is_ordered = True
compare x y = Comparable.from x.to_bits . compare x.to_bits y.to_bits
hash x = Comparable.from x.to_bits . hash x.to_bits

View File

@ -46,17 +46,18 @@ import org.enso.interpreter.runtime.library.dispatch.TypesLibrary;
import org.enso.interpreter.runtime.number.EnsoBigInteger;
import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.runtime.state.State;
import org.enso.polyglot.MethodNames;
@BuiltinMethod(
type = "Comparable",
name = "equals_builtin",
description = """
Compares self with other object and returns True iff `self` is exactly the same as
the other object, including all its transitively accessible properties or fields.
the other object, including all its transitively accessible properties or fields,
False otherwise.
Can handle arbitrary objects, including all foreign objects.
Does not throw exceptions.
Does not throw dataflow errors or panics.
Note that this is different than `Meta.is_same_object`, which checks whether two
references point to the same object on the heap.
@ -65,13 +66,11 @@ import org.enso.polyglot.MethodNames;
@GenerateUncached
public abstract class EqualsNode extends Node {
protected static String EQUALS_MEMBER_NAME = MethodNames.Function.EQUALS;
public static EqualsNode build() {
return EqualsNodeGen.create();
}
public abstract Object execute(@AcceptsError Object left, @AcceptsError Object right);
public abstract boolean execute(@AcceptsError Object left, @AcceptsError Object right);
/**
* Primitive values
@ -134,18 +133,14 @@ public abstract class EqualsNode extends Node {
}
@Specialization
Object equalsDoubleDouble(double self, double other) {
boolean equalsDoubleDouble(double self, double other) {
if (Double.isNaN(self) || Double.isNaN(other)) {
return nothing();
return false;
} else {
return self == other;
}
}
private Object nothing() {
return EnsoContext.get(this).getBuiltins().nothing();
}
@Specialization
boolean equalsDoubleLong(double self, long other) {
return self == (double) other;
@ -268,26 +263,26 @@ public abstract class EqualsNode extends Node {
boolean equalsUnresolvedSymbols(UnresolvedSymbol self, UnresolvedSymbol otherSymbol,
@Cached EqualsNode equalsNode) {
return self.getName().equals(otherSymbol.getName())
&& (boolean) equalsNode.execute(self.getScope(), otherSymbol.getScope());
&& equalsNode.execute(self.getScope(), otherSymbol.getScope());
}
@Specialization
boolean equalsUnresolvedConversion(UnresolvedConversion selfConversion, UnresolvedConversion otherConversion,
@Cached EqualsNode equalsNode) {
return (boolean) equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
}
@Specialization
boolean equalsModuleScopes(ModuleScope selfModuleScope, ModuleScope otherModuleScope,
@Cached EqualsNode equalsNode) {
return (boolean) equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule());
return equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule());
}
@Specialization
@TruffleBoundary
boolean equalsModules(Module selfModule, Module otherModule,
@Cached EqualsNode equalsNode) {
return (boolean) equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString());
return equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString());
}
@Specialization
@ -298,7 +293,7 @@ public abstract class EqualsNode extends Node {
/**
* There is no specialization for {@link TypesLibrary#hasType(Object)}, because also
* primitive values would fall into that specialization and it would be too complicated
* primitive values would fall into that specialization, and it would be too complicated
* to make that specialization disjunctive. So we rather specialize directly for
* {@link Type types}.
*/
@ -309,7 +304,7 @@ public abstract class EqualsNode extends Node {
boolean equalsTypes(Type selfType, Type otherType,
@Cached EqualsNode equalsNode,
@CachedLibrary(limit = "5") TypesLibrary typesLib) {
return (boolean) equalsNode.execute(
return equalsNode.execute(
selfType.getQualifiedName().toString(),
otherType.getQualifiedName().toString()
);
@ -322,7 +317,7 @@ public abstract class EqualsNode extends Node {
@Specialization(guards = {
"selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)"
}, limit = "3")
Object equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
boolean equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
@Cached EqualsNode equalsNode
@ -530,7 +525,7 @@ public abstract class EqualsNode extends Node {
for (long i = 0; i < selfSize; i++) {
Object selfElem = selfInterop.readArrayElement(selfArray, i);
Object otherElem = otherInterop.readArrayElement(otherArray, i);
Object elemsAreEqual;
boolean elemsAreEqual;
if (selfElem instanceof Atom selfAtomElem
&& otherElem instanceof Atom otherAtomElem
&& hasCustomComparatorNode.execute(selfAtomElem)) {
@ -538,7 +533,7 @@ public abstract class EqualsNode extends Node {
} else {
elemsAreEqual = equalsNode.execute(selfElem, otherElem);
}
if (!(elemsAreEqual instanceof Boolean elemsAreEqualBool && elemsAreEqualBool)) {
if (!elemsAreEqual) {
return false;
}
}
@ -573,8 +568,7 @@ public abstract class EqualsNode extends Node {
if (otherInterop.isHashEntryExisting(otherHashMap, key)
&& otherInterop.isHashEntryReadable(otherHashMap, key)) {
Object otherValue = otherInterop.readHashValue(otherHashMap, key);
Object res = equalsNode.execute(selfValue, otherValue);
if (!(res instanceof Boolean resBool && resBool)) {
if (!equalsNode.execute(selfValue, otherValue)) {
return false;
}
} else {
@ -610,8 +604,7 @@ public abstract class EqualsNode extends Node {
for (int i = 0; i < membersSize; i++) {
String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i));
String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i));
Object res = equalsNode.execute(selfMemberName, otherMemberName);
if (!(res instanceof Boolean resBool && resBool)) {
if (!equalsNode.execute(selfMemberName, otherMemberName)) {
return false;
}
memberNames[i] = selfMemberName;
@ -623,8 +616,7 @@ public abstract class EqualsNode extends Node {
interop.isMemberReadable(otherObject, memberNames[i])) {
Object selfMember = interop.readMember(selfObject, memberNames[i]);
Object otherMember = interop.readMember(otherObject, memberNames[i]);
Object res = equalsNode.execute(selfMember, otherMember);
if (!(res instanceof Boolean resBool && resBool)) {
if (!equalsNode.execute(selfMember, otherMember)) {
return false;
}
}
@ -646,11 +638,6 @@ public abstract class EqualsNode extends Node {
return selfConstructor == otherConstructor;
}
/**
* How many {@link EqualsNode} should be created for fields in specialization for atoms.
*/
static final int equalsNodeCountForFields = 10;
static EqualsNode[] createEqualsNodes(int size) {
EqualsNode[] nodes = new EqualsNode[size];
Arrays.fill(nodes, EqualsNode.build());
@ -693,15 +680,10 @@ public abstract class EqualsNode extends Node {
// custom comparators. EqualsNode cannot deal with custom comparators.
fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField);
} else {
Object res = fieldEqualsNodes[i].execute(
fieldsAreEqual = fieldEqualsNodes[i].execute(
selfFields[i],
otherFields[i]
);
if (res instanceof Boolean resBool) {
fieldsAreEqual = resBool;
} else {
return false;
}
}
if (!fieldsAreEqual) {
return false;
@ -728,12 +710,7 @@ public abstract class EqualsNode extends Node {
&& HasCustomComparatorNode.getUncached().execute(selfFieldAtom)) {
areFieldsSame = InvokeAnyEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom);
} else {
Object res = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
if (res instanceof Boolean resBool) {
areFieldsSame = resBool;
} else {
return false;
}
areFieldsSame = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
}
if (!areFieldsSame) {
return false;
@ -766,7 +743,7 @@ public abstract class EqualsNode extends Node {
"isHostFunction(selfHostFunc)",
"isHostFunction(otherHostFunc)"
})
Object equalsHostFunctions(Object selfHostFunc, Object otherHostFunc,
boolean equalsHostFunctions(Object selfHostFunc, Object otherHostFunc,
@CachedLibrary(limit = "5") InteropLibrary interop,
@Cached EqualsNode equalsNode) {
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc);
@ -843,7 +820,7 @@ public abstract class EqualsNode extends Node {
}
/**
* Return true iff object is a primitive value used in some of the specializations
* Return true iff object is a primitive value used in some specializations
* guard. By primitive value we mean any value that can be present in Enso, so,
* for example, not Integer, as that cannot be present in Enso.
* All the primitive types should be handled in their corresponding specializations.
@ -918,10 +895,6 @@ public abstract class EqualsNode extends Node {
}
}
static boolean isAtom(Object object) {
return object instanceof Atom;
}
@TruffleBoundary
boolean isHostObject(Object object) {
return EnsoContext.get(this).getEnvironment().isHostObject(object);

View File

@ -4,4 +4,4 @@ import org.enso.interpreter.dsl.BuiltinType;
import org.enso.interpreter.node.expression.builtin.Builtin;
@BuiltinType
public class DefaultOrderedComparator extends Builtin {}
public class DefaultComparator extends Builtin {}

View File

@ -1,7 +0,0 @@
package org.enso.interpreter.node.expression.builtin.ordering;
import org.enso.interpreter.dsl.BuiltinType;
import org.enso.interpreter.node.expression.builtin.Builtin;
@BuiltinType
public class DefaultUnorderedComparator extends Builtin {}

View File

@ -31,8 +31,7 @@ public abstract class HasCustomComparatorNode extends Node {
/**
* Returns true if the given atom has a custom comparator, that is a comparator that is different
* than the default (internal) ones. The default comparators are {@code
* Default_Unordered_Comparator} and {@code Default_Ordered_Comparator}.
* than the default (internal) ones.
*
* @param atom Atom for which we check whether it has custom comparator
* @return true iff the given atom has a custom comparator

View File

@ -27,7 +27,7 @@ import org.enso.interpreter.runtime.number.EnsoBigInteger;
type = "Comparable",
name = "less_than_builtin",
description = """
Returns true if self is less than `other`. Or throw an error if the values are
Returns true if self is less than `other`. Or return Nothing if the values are
not comparable.
"""
)
@ -328,11 +328,6 @@ public abstract class LessThanNode extends Node {
return nothing();
}
private DataflowError dataflowError(Object left, Object right) {
var typeError = EnsoContext.get(this).getBuiltins().error().makeTypeError(left, right, "right");
return DataflowError.withoutTrace(typeError, this);
}
private Object nothing() {
return EnsoContext.get(this).getNothing();
}

View File

@ -30,8 +30,7 @@ import org.enso.interpreter.node.expression.builtin.mutable.Array;
import org.enso.interpreter.node.expression.builtin.mutable.Ref;
import org.enso.interpreter.node.expression.builtin.immutable.Vector;
import org.enso.interpreter.node.expression.builtin.ordering.Comparable;
import org.enso.interpreter.node.expression.builtin.ordering.DefaultOrderedComparator;
import org.enso.interpreter.node.expression.builtin.ordering.DefaultUnorderedComparator;
import org.enso.interpreter.node.expression.builtin.ordering.DefaultComparator;
import org.enso.interpreter.node.expression.builtin.ordering.Ordering;
import org.enso.interpreter.node.expression.builtin.resource.ManagedResource;
import org.enso.interpreter.node.expression.builtin.text.Text;
@ -86,8 +85,7 @@ public class Builtins {
private final Boolean bool;
private final Ordering ordering;
private final Comparable comparable;
private final DefaultOrderedComparator defaultOrderedComparator;
private final DefaultUnorderedComparator defaultUnorderedComparator;
private final DefaultComparator defaultComparator;
private final System system;
private final Special special;
@ -135,8 +133,7 @@ public class Builtins {
error = new Error(this, context);
ordering = getBuiltinType(Ordering.class);
comparable = getBuiltinType(Comparable.class);
defaultUnorderedComparator = getBuiltinType(DefaultUnorderedComparator.class);
defaultOrderedComparator = getBuiltinType(DefaultOrderedComparator.class);
defaultComparator = getBuiltinType(DefaultComparator.class);
system = new System(this);
number = new Number(this);
bool = this.getBuiltinType(Boolean.class);
@ -598,12 +595,8 @@ public class Builtins {
return comparable;
}
public DefaultOrderedComparator defaultOrderedComparator() {
return defaultOrderedComparator;
}
public DefaultUnorderedComparator defaultUnorderedComparator() {
return defaultUnorderedComparator;
public DefaultComparator defaultComparator() {
return defaultComparator;
}
/** @return the container for the dataflow error-related builtins */

View File

@ -1,7 +1,6 @@
package org.enso.interpreter.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.time.LocalDate;
@ -14,8 +13,6 @@ import java.util.stream.Collectors;
import org.enso.interpreter.node.expression.builtin.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNodeGen;
import org.enso.interpreter.runtime.EnsoContext;
import org.enso.interpreter.runtime.data.Type;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
import org.junit.AfterClass;
@ -92,17 +89,9 @@ public class EqualsTest extends TestBase {
executeInContext(
context,
() -> {
Object firstResult = equalsNode.execute(firstValue, secondValue);
Object secondResult = equalsNode.execute(secondValue, firstValue);
if (firstResult instanceof Boolean firstResultBool && secondResult instanceof Boolean secondResultBool) {
assertEquals("equals should be symmetric", firstResultBool, secondResultBool);
} else {
var nothing = EnsoContext.get(null).getBuiltins().nothing();
assertTrue(
"equals should be symmetric - Nothing returned just in one case",
nothing == firstResult && nothing == secondResult
);
}
boolean firstResult = equalsNode.execute(firstValue, secondValue);
boolean secondResult = equalsNode.execute(secondValue, firstValue);
assertEquals("equals should be symmetric", firstResult, secondResult);
return null;
});
}
@ -146,7 +135,7 @@ public class EqualsTest extends TestBase {
executeInContext(
context,
() -> {
assertTrue((Boolean) equalsNode.execute(ensoDate, javaDate));
assertTrue(equalsNode.execute(ensoDate, javaDate));
return null;
});
}
@ -164,7 +153,7 @@ public class EqualsTest extends TestBase {
executeInContext(
context,
() -> {
assertTrue((Boolean) equalsNode.execute(ensoTime, javaDate));
assertTrue(equalsNode.execute(ensoTime, javaDate));
return null;
});
}
@ -187,7 +176,7 @@ public class EqualsTest extends TestBase {
executeInContext(
context,
() -> {
assertTrue((Boolean) equalsNode.execute(ensoDateTime, javaDateTime));
assertTrue(equalsNode.execute(ensoDateTime, javaDateTime));
return null;
});
}
@ -200,26 +189,8 @@ public class EqualsTest extends TestBase {
executeInContext(
context,
() -> {
assertTrue((Boolean) equalsNode.execute(ensoVector, javaVector));
assertTrue(equalsNode.execute(ensoVector, javaVector));
return null;
});
}
/**
* NaN is a special value of Decimal type that is not comparable.
*/
@Test
public void testNanEquality() {
Object nan = unwrapValue(context, createValue(context, "Number.nan", "import Standard.Base.Data.Numbers.Number"));
Object decimal = unwrapValue(context, createValue(context, "15.56"));
executeInContext(
context,
() -> {
Object res = equalsNode.execute(nan, decimal);
Type nothing = EnsoContext.get(null).getNothing();
assertSame("Comparison of NaN and other decimal should return Nothing", nothing, res);
return null;
}
);
}
}

View File

@ -8,8 +8,6 @@ type My
Data x
type My_Comparator
is_ordered = True
compare my_1 my_2 =
comparator = Comparable.from my_2.x
comparator.compare my_2.x my_1.x

View File

@ -16,8 +16,6 @@ type My_Type
Value x y
type My_Type_Comparator
is_ordered = True
compare my_1 my_2 =
comp = Comparable.from my_1.x
comp.compare (my_1.x + my_1.y) (my_2.x + my_2.y)

View File

@ -30,8 +30,6 @@ type My
My.Data x1 y1 -> My.Data y1 x1
type My_Comparator
is_ordered = True
compare left right =
left_sum = left.x + left.y
right_sum = right.x + right.y

View File

@ -3,7 +3,6 @@ import Standard.Base.Error.Illegal_Argument.Illegal_Argument
import Standard.Base.Error.No_Such_Key.No_Such_Key
import Standard.Base.Data.Time.Date_Time.Date_Time
from Standard.Base.Data.Map import Map
from Standard.Base.Data.Ordering import Default_Unordered_Comparator
from Standard.Test import Test, Test_Suite, Problems
import Standard.Test.Extensions
@ -76,10 +75,10 @@ spec =
empty_map.is_empty . should_be_true
non_empty.is_empty . should_be_false
Test.specify "should get default unordered comparator for polyglot maps" <|
Comparable.from (Map.empty) . should_equal Default_Unordered_Comparator
Comparable.from (js_empty_dict) . should_equal Default_Unordered_Comparator
Comparable.from (JavaMap.of "A" 1 "B" 2) . should_equal Default_Unordered_Comparator
Test.specify "should get the default comparator for polyglot maps" <|
Comparable.from (Map.empty) . should_equal Default_Comparator
Comparable.from (js_empty_dict) . should_equal Default_Comparator
Comparable.from (JavaMap.of "A" 1 "B" 2) . should_equal Default_Comparator
Test.specify "should compare two hash maps" <|
(Map.singleton "a" 1).should_equal (Map.singleton "a" 1)

View File

@ -385,7 +385,7 @@ spec =
(very_negative >= hundred_factorial).should_be_false
(very_negative >= Nothing).should_fail_with Type_Error
Test.specify "should be ordered by Default_Ordered_Comparator" <|
Test.specify "should be ordered by the default comparator" <|
Ordering.compare 1 2 . should_equal Ordering.Less
Ordering.compare 1 1 . should_equal Ordering.Equal
Ordering.compare 1 0 . should_equal Ordering.Greater

View File

@ -15,7 +15,6 @@ type Ord
# The comparison is reverted, i.e., `x < y` gives result for `y.number < x.number`.
type Ord_Comparator
is_ordered = True
compare x y = (Comparable.from y.number) . compare y.number x.number
hash x = (Comparable.from x.number) . hash x.number
@ -24,7 +23,11 @@ Comparable.from (_:Ord) = Ord_Comparator
type No_Ord
Value number
Comparable.from (_:No_Ord) = Incomparable
type No_Ord_Comparator
compare x y = Nothing
hash x = 0
Comparable.from (_:No_Ord) = No_Ord_Comparator
# Tests
@ -67,7 +70,7 @@ spec = Test.group "Object Comparator" <|
((default_comparator (Ord.Value 1) (Ord.Value 1)) == 0) . should_equal True
Test.specify "should fail gracefully for incomparable items" <|
(default_comparator 1 True).catch . should_equal (Incomparable_Values.Error 1 True)
(default_comparator 1 True) . should_fail_with Incomparable_Values
(default_comparator (No_Ord.Value 1) (No_Ord.Value 2)).should_fail_with Incomparable_Values
main = Test_Suite.run_main spec

View File

@ -11,7 +11,6 @@ type Ord
Value number
type Ord_Comparator
is_ordered = True
compare x y = (Comparable.from x.number) . compare x.number y.number
hash x = (Comparable.from x.number) . hash x.number
@ -22,12 +21,10 @@ type UPair
Value x y
type UPair_Comparator
is_ordered = False
equals pair1 pair2 =
if pair1.x == pair2.x && pair1.y == pair2.y then True else
if pair1.x == pair2.y && pair1.y == pair2.x then True else
False
compare pair1 pair2 =
if pair1.x == pair2.x && pair1.y == pair2.y then Ordering.Equal else
if pair1.x == pair2.y && pair1.y == pair2.x then Ordering.Equal else
Nothing
hash upair =
x_comp = Comparable.from upair.x
@ -42,24 +39,17 @@ type Parent
# === The Tests ===
spec =
Test.group "Default ordered comparator" <|
Test.specify "should support custom ordered comparator" <|
Test.group "Default comparator" <|
Test.specify "should support custom comparator" <|
Ordering.compare (Ord.Value 1) (Ord.Value 2) . should_equal Ordering.Less
Ordering.compare (Ord.Value 1) (Ord.Value 1) . should_equal Ordering.Equal
Ordering.compare (Ord.Value 20) (Ord.Value 1) . should_equal Ordering.Greater
Ordering.compare (UPair.Value 1 2) (UPair.Value 2 1) . should_equal Ordering.Equal
Test.specify "should support equality for custom ordered comparators in atom field" <|
Test.specify "should support equality for custom comparators in atom field" <|
((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 1))) . should_be_true
((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 22))) . should_be_false
Test.specify "should throw Incomparable_Values when comparing types with unordered comparator" <|
val1 = (UPair.Value 1 2)
val2 = (UPair.Value 2 1)
err = Ordering.compare val1 val2
err.should_fail_with Incomparable_Values
Meta.is_same_object err.catch.left val1 . should_be_true
Meta.is_same_object err.catch.right val2 . should_be_true
Test.specify "should throw Type_Error when comparing different types" <|
Ordering.compare (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Type_Error
Ordering.compare 1 Nothing . should_fail_with Type_Error

View File

@ -12,7 +12,6 @@ type Ord
Value number
type Ord_Comparator
is_ordered = True
compare x y = (Comparable.from x.number) . compare x.number y.number
hash x = (Comparable.from x.number) . hash x.number
@ -21,7 +20,11 @@ Comparable.from (_:Ord) = Ord_Comparator
type No_Ord
Value number
Comparable.from (_:No_Ord) = Incomparable
type No_Ord_Comparator
compare x y = Nothing
hash x = 0
Comparable.from (_:No_Ord) = No_Ord_Comparator
# Tests

View File

@ -3,6 +3,7 @@ import Standard.Base.Data.Text.Regex_2.No_Such_Group
import Standard.Base.Data.Text.Regex_2.Regex_Syntax_Error
import Standard.Base.Data.Text.Span.Span
import Standard.Base.Error.Common.Index_Out_Of_Bounds
import Standard.Base.Error.Common.Incomparable_Values
import Standard.Base.Error.Common.Type_Error
import Standard.Base.Error.Illegal_Argument.Illegal_Argument
@ -120,8 +121,8 @@ spec =
# Handling of Nothing
(accent_1 == Nothing) . should_be_false
(accent_1 != Nothing) . should_be_true
Ordering.compare accent_1 Nothing . should_fail_with Type_Error
(accent_1 > Nothing) . should_fail_with Type_Error
Ordering.compare accent_1 Nothing . should_fail_with Incomparable_Values
(accent_1 > Nothing) . should_fail_with Incomparable_Values
accent_1 . compare_to_ignore_case Nothing . should_fail_with Type_Error
earlier_suffix = "aooooz"

View File

@ -123,9 +123,6 @@ spec_with name create_new_date parse_date =
result ->
Test.fail ("Unexpected result: " + result.to_text)
Test.specify "should get default ordered comparator for dates" <|
Comparable.from (create_new_date 2023 2 3) . should_equal Default_Ordered_Comparator
Test.specify "should be comparable" <|
date_1 = parse_date "2021-01-02"
date_2 = parse_date "2021-01-01"

View File

@ -315,10 +315,10 @@ spec_with name create_new_datetime parse_datetime nanoseconds_loss_in_precision=
time . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should get default ordered comparator for datetimes" <|
Comparable.from (create_new_datetime 2023 2 3 23 59) . should_equal Default_Ordered_Comparator
Comparable.from (parse_datetime "2021-01-01T00:30:12.7102[UTC]") . should_equal Default_Ordered_Comparator
Comparable.from (create_new_datetime 2022 10 31 2 30 55 1234) . should_equal Default_Ordered_Comparator
Test.specify "should get the default comparator for datetimes" <|
Comparable.from (create_new_datetime 2023 2 3 23 59) . should_equal Default_Comparator
Comparable.from (parse_datetime "2021-01-01T00:30:12.7102[UTC]") . should_equal Default_Comparator
Comparable.from (create_new_datetime 2022 10 31 2 30 55 1234) . should_equal Default_Comparator
Test.specify "should be comparable" <|
time_1 = parse_datetime "2021-01-01T00:30:12.7102[UTC]"

View File

@ -1,5 +1,5 @@
from Standard.Base import all
import Standard.Base.Error.Common.Type_Error
import Standard.Base.Error.Common.Incomparable_Values
import Standard.Base.Error.Time_Error.Time_Error
from Standard.Test import Test, Test_Suite
@ -62,8 +62,8 @@ spec =
(duration - period).should_fail_with Time_Error
(period + duration).should_fail_with Time_Error
(period - duration).should_fail_with Time_Error
(duration > period).should_fail_with Type_Error
(duration < period).should_fail_with Type_Error
(duration > period).should_fail_with Incomparable_Values
(duration < period).should_fail_with Incomparable_Values
Test.specify "Date_Time supports adding and subtracting Duration" <|
((Date_Time.new 2022 10 1 hour=10) + (Duration.new hours=2)) . should_equal (Date_Time.new 2022 10 1 hour=12)

View File

@ -17,7 +17,6 @@ type T
Value a b
type T_Comparator
is_ordered = True
compare t1 t2 = Comparable.from t1.a . compare t1.a t2.a
hash t = Comparable.from t.a . hash t.a

View File

@ -15,8 +15,7 @@ type CustomEqType
CustomEqType.C2 f1 f2 -> f1 + f2
type CustomEqType_Comparator
is_ordered = False
equals o1 o2 = o1.sum == o2.sum
compare o1 o2 = if o1.sum == o2.sum then Ordering.Equal else Nothing
hash o =
comp = Comparable.from o.sum
comp.hash o.sum
@ -27,8 +26,7 @@ type Child
Value number
type Child_Comparator
is_ordered = False
equals child1 child2 = child1.number % 100 == child2.number % 100
compare child1 child2 = if child1.number % 100 == child2.number % 100 then Ordering.Equal else Nothing
hash child =
comp = Comparable.from child.number
comp.hash (child.number % 100)