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

View File

@ -108,26 +108,19 @@ type Any
== self that = == self that =
# If there is No_Such_Conversion, then `self` and `that` are probably # 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. # 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_self = Panic.catch No_Such_Conversion (Comparable.from self) _-> Default_Comparator
eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Unordered_Comparator eq_that = Panic.catch No_Such_Conversion (Comparable.from that) _-> Default_Comparator
case Meta.is_same_object eq_self Incomparable of similar_type = Meta.is_same_object eq_self eq_that
True -> False case similar_type of
False -> False -> False
similar_type = Meta.is_same_object eq_self eq_that True ->
case similar_type of case Meta.is_same_object eq_self Default_Comparator of
False -> False # Shortcut for objects with Default_Comparator, because of the performance.
True -> True -> Comparable.equals_builtin self that
case eq_self.is_ordered of False ->
True -> case eq_self.compare self that of
res = eq_self.compare self that Ordering.Equal -> True
case res of _ -> False
Ordering.Equal -> True
_ -> False
False ->
res = eq_self.equals self that
case res of
Nothing -> False
_ -> res
## ALIAS Inequality ## ALIAS Inequality
@ -159,8 +152,10 @@ type Any
Arguments: Arguments:
- that: The value to compare `self` against. - that: The value to compare `self` against.
To have `>` properly defined, a type must have an associated ordered comparator. To be comparable, a custom object must have an associated comparator
See `Ordering.enso` for information how comparators work. 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 > Example
Checking if the variable `a` is greater than `147`. Checking if the variable `a` is greater than `147`.
@ -172,7 +167,7 @@ type Any
a > 147 a > 147
> : Any -> Boolean ! Incomparable_Values > : Any -> Boolean ! Incomparable_Values
> self that = > self that =
assert_ordered_comparators self that <| assert_same_comparators self that <|
case (Comparable.from self).compare self that of case (Comparable.from self).compare self that of
Ordering.Greater -> True Ordering.Greater -> True
Nothing -> Error.throw (Incomparable_Values.Error self that) Nothing -> Error.throw (Incomparable_Values.Error self that)
@ -185,8 +180,6 @@ type Any
Arguments: Arguments:
- that: The value to compare `self` against. - that: The value to compare `self` against.
To have `>=` defined, a type must define both `>` and `==`.
! Implementing Greater Than or Equal ! Implementing Greater Than or Equal
While it is often possible to implement a more efficient version of this 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 operation for complex types, care must be taken to ensure that your
@ -203,12 +196,12 @@ type Any
a >= 147 a >= 147
>= : Any -> Boolean ! Incomparable_Values >= : Any -> Boolean ! Incomparable_Values
>= self that = >= self that =
assert_ordered_comparators self that <| assert_same_comparators self that <|
case (Comparable.from self).compare self that of case (Comparable.from self).compare self that of
Ordering.Less -> False
Ordering.Equal -> True Ordering.Equal -> True
Ordering.Greater -> True Ordering.Greater -> True
Nothing -> Error.throw (Incomparable_Values.Error self that) Nothing -> Error.throw (Incomparable_Values.Error self that)
_ -> False
## ALIAS Less Than ## ALIAS Less Than
@ -217,8 +210,10 @@ type Any
Arguments: Arguments:
- that: The value to compare `self` against. - that: The value to compare `self` against.
To have `<` properly defined, a type must have an associated ordered comparator. To be comparable, a custom object must have an associated comparator
See `Ordering.enso` for information how comparators work. 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 > Example
Checking if the variable `a` is less than `147`. Checking if the variable `a` is less than `147`.
@ -230,7 +225,7 @@ type Any
a < 147 a < 147
< : Any -> Boolean ! Incomparable_Values < : Any -> Boolean ! Incomparable_Values
< self that = < self that =
assert_ordered_comparators self that <| assert_same_comparators self that <|
case (Comparable.from self).compare self that of case (Comparable.from self).compare self that of
Ordering.Less -> True Ordering.Less -> True
Nothing -> Error.throw (Incomparable_Values.Error self that) Nothing -> Error.throw (Incomparable_Values.Error self that)
@ -261,12 +256,12 @@ type Any
a < 147 a < 147
<= : Any -> Boolean ! Incomparable_Values <= : Any -> Boolean ! Incomparable_Values
<= self that = <= self that =
assert_ordered_comparators self that <| assert_same_comparators self that <|
case (Comparable.from self).compare self that of case (Comparable.from self).compare self that of
Ordering.Less -> True
Ordering.Equal -> True Ordering.Equal -> True
Ordering.Greater -> False Ordering.Less -> True
Nothing -> Error.throw (Incomparable_Values.Error self that) Nothing -> Error.throw (Incomparable_Values.Error self that)
_ -> False
## Checks if the type is an instance of `Nothing`. ## Checks if the type is an instance of `Nothing`.
@ -456,13 +451,12 @@ type Any
## PRIVATE ## PRIVATE
Checks if both comparators of the given objects are both of same type and ordered. Checks if the comparators for the given objects are both of the same type. If so,
If they are not of same type, a `Type_Error` is thrown. proceeds with the given action, and if not, throws `Type_Error`.
If the comparators are either `Incomparable`, or unordered, `False` is returned. assert_same_comparators : Any -> Any -> (Any -> Any) -> Any ! Type_Error
assert_ordered_comparators : Any -> (Any -> Any) -> Any ! (Type_Error | Incomparable_Values) assert_same_comparators this that ~action =
assert_ordered_comparators this that ~action =
comp_this = Comparable.from this comp_this = Comparable.from this
comp_that = Comparable.from that 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 case Meta.is_same_object comp_this comp_that of
if Meta.is_same_object comp_this Incomparable || comp_this.is_ordered.not then Error.throw (Incomparable_Values.Error this that) else True -> action
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.Any.Any
import project.Data.Ordering.Ordering import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Nothing.Nothing import project.Nothing.Nothing
from project.Data.Boolean.Boolean import True, False from project.Data.Boolean.Boolean import True, False
@ -90,5 +89,3 @@ type Boolean
if_then : Any -> Any | Nothing if_then : Any -> Any | Nothing
if_then self ~on_true = @Builtin_Method "Boolean.if_then" 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 ## PRIVATE
type JS_Object_Comparator type JS_Object_Comparator
is_ordered : Boolean compare : JS_Object -> JS_Object -> (Ordering|Nothing)
is_ordered = False compare obj1 obj2 =
equals : JS_Object -> JS_Object -> Boolean
equals obj1 obj2 =
obj1_keys = obj1.field_names obj1_keys = obj1.field_names
obj2_keys = obj2.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 (obj1.get key == obj2.at key).catch No_Such_Key _->False
if same_values then Ordering.Equal else Nothing
hash : JS_Object -> Integer hash : JS_Object -> Integer
hash obj = 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.Text.Text
import project.Data.Locale.Locale import project.Data.Locale.Locale
import project.Error.Common.Arithmetic_Error import project.Error.Common.Arithmetic_Error
@ -941,8 +937,6 @@ type Integer
parse_builtin text radix = @Builtin_Method "Integer.parse" parse_builtin text radix = @Builtin_Method "Integer.parse"
Comparable.from (_:Number) = Default_Ordered_Comparator
## UNSTABLE ## UNSTABLE
A syntax error when parsing a double. 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: 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 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 type Comparator T
is_ordered = True
compare : T -> T -> (Ordering|Nothing) compare : T -> T -> (Ordering|Nothing)
hash : T -> Integer 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 A comparator has to implement `compare` and `hash` methods. `compare x y` method
`Comparable.from (_:My_Type)` must always return the same comparator type. returns `Ordering.Less` if x is ordered before y, `Ordering.Equal` if x is equal
Note that if there is an implicit `Default_Unordered_Comparator` assigned to y, `Ordering.Greater` if x is ordered after y, and `Nothing` if x and y are
for every type by default. not equal, and their relative ordering cannot be determined.
Note that there has to be `is_ordered` method defined which returns a Boolean The signature of the `compare` method is designed so that it enables comparators
indicating that the comparator is ordered. This is currently needed as there is to provide a _partial ordering_ for a specific type.
no way to define interfaces in Enso.
An _unordered comparator_ has to implement both `equals` and `hash` to define A hash code must be provided for all the objects, therefore, the type signature
a _partial_ custom equality. By _partial_, we mean that every instance of the type of `hash` method does not allow `Nothing` as a return value.
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.
The runtime expects the following semantics for all the comparators: The runtime expects the following semantics for all the comparators:
- Hash consistency: - Hash consistency:
@ -85,12 +72,10 @@ from project.Data.Boolean import all
Value x y Value x y
type UPair_Comparator type UPair_Comparator
is_ordered = False compare pair1 pair2 =
if pair1.x == pair2.x && pair1.y == pair2.y then Ordering.Equal else
equals pair1 pair2 = if pair1.x == pair2.y && pair1.y == pair2.x then Ordering.Equal else
if pair1.x == pair2.x && pair1.y == pair2.y then True else Nothing
if pair1.x == pair2.y && pair1.y == pair2.x then True else
False
hash upair = hash upair =
x_comp = Comparable.from upair.x x_comp = Comparable.from upair.x
@ -111,7 +96,6 @@ from project.Data.Boolean import all
Comparable.from (_:Rational) = Rational_Ordering Comparable.from (_:Rational) = Rational_Ordering
type Rational_Ordering type Rational_Ordering
is_ordered = True
compare self r1 r2 = compare self r1 r2 =
v1 = r1.numerator * r2.denominator v1 = r1.numerator * r2.denominator
v2 = r2.numerator * r1.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 By defining the `Rational_Ordering` and making it available via
`Comparable.from (_:Rational)` method, all parts of the Enso system will use `Comparable.from (_:Rational)` method, all parts of the Enso system will use
the custom comparator whenever equality or hash code is needed. 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 @Builtin_Type
type Comparable type Comparable
## PRIVATE ## PRIVATE
Called as a callback from internal engine code for an atom with a custom 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 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 -> Integer
hash_callback atom = (Comparable.from atom).hash atom hash_callback atom = (Comparable.from atom).hash atom
@ -146,52 +123,30 @@ type Comparable
has_custom_comparator : Atom -> Boolean has_custom_comparator : Atom -> Boolean
has_custom_comparator atom = has_custom_comparator atom =
comp = Comparable.from 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. ## Default implementation of a _comparator_.
type Incomparable
Singleton
## Default implementation of unordered comparator.
@Builtin_Type @Builtin_Type
type Default_Unordered_Comparator type Default_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.
compare : Any -> Any -> (Ordering|Nothing) compare : Any -> Any -> (Ordering|Nothing)
compare x y = compare x y =
case Comparable.less_than_builtin x y of case Comparable.equals_builtin x y of
Nothing -> Nothing True -> Ordering.Equal
True -> Ordering.Less
False -> False ->
case Comparable.equals_builtin x y of case Comparable.less_than_builtin x y of
Nothing -> Nothing Nothing -> Nothing
True -> Ordering.Equal True -> Ordering.Less
False -> False ->
case Comparable.less_than_builtin y x of case Comparable.less_than_builtin y x of
Nothing -> Nothing Nothing -> Nothing
True -> Ordering.Greater True -> Ordering.Greater
False -> Panic.throw (Illegal_State.Error "Unreachable") False -> Nothing
hash : Number -> Integer hash : Number -> Integer
hash x = Comparable.hash_builtin x hash x = Comparable.hash_builtin x
Comparable.from (_:Any) = Default_Unordered_Comparator Comparable.from (_:Any) = Default_Comparator
## Types representing the ordering of values. ## Types representing the ordering of values.
@ -250,7 +205,6 @@ type Ordering
if sign > 0 then Ordering.Greater else Ordering.Less if sign > 0 then Ordering.Greater else Ordering.Less
type Ordering_Comparator type Ordering_Comparator
is_ordered = True
compare x y = (Comparable.from x.to_sign).compare x.to_sign y.to_sign compare x y = (Comparable.from x.to_sign).compare x.to_sign y.to_sign
hash x = x.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.Error.Error
import project.Meta import project.Meta
from project.Data.Ordering import all
from project.Data.Boolean import Boolean, True, False from project.Data.Boolean import Boolean, True, False
polyglot java import org.enso.base.Text_Utils polyglot java import org.enso.base.Text_Utils
@ -118,5 +117,3 @@ type Text
Conversion to Text that overrides the default `to_text` behavior. Conversion to Text that overrides the default `to_text` behavior.
to_text : Text to_text : Text
to_text self = self 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.Numbers.Integer
import project.Data.Ordering.Ordering import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text import project.Data.Text.Text
import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Period.Date_Period
import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Date_Time.Date_Time
@ -627,6 +626,3 @@ is_weekend date =
## PRIVATE ## PRIVATE
fits_in_range start end date = fits_in_range start end date =
(start <= date) && (date < end) (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.Numbers.Integer
import project.Data.Ordering.Ordering import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text import project.Data.Text.Text
import project.Data.Time.Date.Date import project.Data.Time.Date.Date
import project.Data.Time.Date_Period.Date_Period import project.Data.Time.Date_Period.Date_Period
@ -687,5 +686,3 @@ type Date_Time
format : Text -> Text format : Text -> Text
format self pattern = @Builtin_Method "Date_Time.format" 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.Decimal
import project.Data.Numbers.Integer import project.Data.Numbers.Integer
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Pair.Pair import project.Data.Pair.Pair
import project.Data.Time.Date_Time.Date_Time import project.Data.Time.Date_Time.Date_Time
import project.Data.Time.Period.Period import project.Data.Time.Period.Period
@ -294,5 +293,3 @@ type Duration
example_is_empty = Duration.zero.is_empty example_is_empty = Duration.zero.is_empty
is_empty : Boolean is_empty : Boolean
is_empty self = self.to_vector . all (==0) 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.Date.Date
import project.Data.Time.Duration.Duration import project.Data.Time.Duration.Duration
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Incomparable
import project.Data.Text.Text import project.Data.Text.Text
import project.Error.Error import project.Error.Error
import project.Error.Illegal_Argument.Illegal_Argument 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.Locale.Locale
import project.Data.Numbers.Integer import project.Data.Numbers.Integer
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Text.Text import project.Data.Text.Text
import project.Data.Time.Date.Date import project.Data.Time.Date.Date
import project.Data.Time.Date_Time.Date_Time 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" example_format = Time_Of_Day.new 16 21 10 . format "'hour:'h"
format : Text -> Text format : Text -> Text
format self pattern = @Builtin_Method "Time_Of_Day.format" 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.Natural_Order
import project.Data.Ordering.Ordering import project.Data.Ordering.Ordering
import project.Data.Ordering.Comparable import project.Data.Ordering.Comparable
import project.Data.Ordering.Incomparable import project.Data.Ordering.Default_Comparator
import project.Data.Ordering.Default_Ordered_Comparator
import project.Data.Ordering.Sort_Direction.Sort_Direction import project.Data.Ordering.Sort_Direction.Sort_Direction
import project.Data.Pair.Pair import project.Data.Pair.Pair
import project.Data.Range.Range import project.Data.Range.Range
@ -131,8 +130,7 @@ export project.Data.Maybe.Maybe
export project.Data.Ordering.Natural_Order export project.Data.Ordering.Natural_Order
export project.Data.Ordering.Ordering export project.Data.Ordering.Ordering
export project.Data.Ordering.Comparable export project.Data.Ordering.Comparable
export project.Data.Ordering.Incomparable export project.Data.Ordering.Default_Comparator
export project.Data.Ordering.Default_Ordered_Comparator
export project.Data.Ordering.Sort_Direction.Sort_Direction export project.Data.Ordering.Sort_Direction.Sort_Direction
export project.Data.Pair.Pair export project.Data.Pair.Pair
export project.Data.Range.Range export project.Data.Range.Range

View File

@ -24,7 +24,6 @@ type Bits
Bits.Bits_64 -> 64 Bits.Bits_64 -> 64
type Bits_Comparator type Bits_Comparator
is_ordered = True
compare x y = Comparable.from x.to_bits . compare x.to_bits y.to_bits 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 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.number.EnsoBigInteger;
import org.enso.interpreter.runtime.scope.ModuleScope; import org.enso.interpreter.runtime.scope.ModuleScope;
import org.enso.interpreter.runtime.state.State; import org.enso.interpreter.runtime.state.State;
import org.enso.polyglot.MethodNames;
@BuiltinMethod( @BuiltinMethod(
type = "Comparable", type = "Comparable",
name = "equals_builtin", name = "equals_builtin",
description = """ description = """
Compares self with other object and returns True iff `self` is exactly the same as 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. 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 Note that this is different than `Meta.is_same_object`, which checks whether two
references point to the same object on the heap. references point to the same object on the heap.
@ -65,13 +66,11 @@ import org.enso.polyglot.MethodNames;
@GenerateUncached @GenerateUncached
public abstract class EqualsNode extends Node { public abstract class EqualsNode extends Node {
protected static String EQUALS_MEMBER_NAME = MethodNames.Function.EQUALS;
public static EqualsNode build() { public static EqualsNode build() {
return EqualsNodeGen.create(); 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 * Primitive values
@ -134,18 +133,14 @@ public abstract class EqualsNode extends Node {
} }
@Specialization @Specialization
Object equalsDoubleDouble(double self, double other) { boolean equalsDoubleDouble(double self, double other) {
if (Double.isNaN(self) || Double.isNaN(other)) { if (Double.isNaN(self) || Double.isNaN(other)) {
return nothing(); return false;
} else { } else {
return self == other; return self == other;
} }
} }
private Object nothing() {
return EnsoContext.get(this).getBuiltins().nothing();
}
@Specialization @Specialization
boolean equalsDoubleLong(double self, long other) { boolean equalsDoubleLong(double self, long other) {
return self == (double) other; return self == (double) other;
@ -268,26 +263,26 @@ public abstract class EqualsNode extends Node {
boolean equalsUnresolvedSymbols(UnresolvedSymbol self, UnresolvedSymbol otherSymbol, boolean equalsUnresolvedSymbols(UnresolvedSymbol self, UnresolvedSymbol otherSymbol,
@Cached EqualsNode equalsNode) { @Cached EqualsNode equalsNode) {
return self.getName().equals(otherSymbol.getName()) return self.getName().equals(otherSymbol.getName())
&& (boolean) equalsNode.execute(self.getScope(), otherSymbol.getScope()); && equalsNode.execute(self.getScope(), otherSymbol.getScope());
} }
@Specialization @Specialization
boolean equalsUnresolvedConversion(UnresolvedConversion selfConversion, UnresolvedConversion otherConversion, boolean equalsUnresolvedConversion(UnresolvedConversion selfConversion, UnresolvedConversion otherConversion,
@Cached EqualsNode equalsNode) { @Cached EqualsNode equalsNode) {
return (boolean) equalsNode.execute(selfConversion.getScope(), otherConversion.getScope()); return equalsNode.execute(selfConversion.getScope(), otherConversion.getScope());
} }
@Specialization @Specialization
boolean equalsModuleScopes(ModuleScope selfModuleScope, ModuleScope otherModuleScope, boolean equalsModuleScopes(ModuleScope selfModuleScope, ModuleScope otherModuleScope,
@Cached EqualsNode equalsNode) { @Cached EqualsNode equalsNode) {
return (boolean) equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule()); return equalsNode.execute(selfModuleScope.getModule(), otherModuleScope.getModule());
} }
@Specialization @Specialization
@TruffleBoundary @TruffleBoundary
boolean equalsModules(Module selfModule, Module otherModule, boolean equalsModules(Module selfModule, Module otherModule,
@Cached EqualsNode equalsNode) { @Cached EqualsNode equalsNode) {
return (boolean) equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString()); return equalsNode.execute(selfModule.getName().toString(), otherModule.getName().toString());
} }
@Specialization @Specialization
@ -298,7 +293,7 @@ public abstract class EqualsNode extends Node {
/** /**
* There is no specialization for {@link TypesLibrary#hasType(Object)}, because also * 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 * to make that specialization disjunctive. So we rather specialize directly for
* {@link Type types}. * {@link Type types}.
*/ */
@ -309,7 +304,7 @@ public abstract class EqualsNode extends Node {
boolean equalsTypes(Type selfType, Type otherType, boolean equalsTypes(Type selfType, Type otherType,
@Cached EqualsNode equalsNode, @Cached EqualsNode equalsNode,
@CachedLibrary(limit = "5") TypesLibrary typesLib) { @CachedLibrary(limit = "5") TypesLibrary typesLib) {
return (boolean) equalsNode.execute( return equalsNode.execute(
selfType.getQualifiedName().toString(), selfType.getQualifiedName().toString(),
otherType.getQualifiedName().toString() otherType.getQualifiedName().toString()
); );
@ -322,7 +317,7 @@ public abstract class EqualsNode extends Node {
@Specialization(guards = { @Specialization(guards = {
"selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)" "selfWarnLib.hasWarnings(selfWithWarnings) || otherWarnLib.hasWarnings(otherWithWarnings)"
}, limit = "3") }, limit = "3")
Object equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings, boolean equalsWithWarnings(Object selfWithWarnings, Object otherWithWarnings,
@CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib, @CachedLibrary("selfWithWarnings") WarningsLibrary selfWarnLib,
@CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib, @CachedLibrary("otherWithWarnings") WarningsLibrary otherWarnLib,
@Cached EqualsNode equalsNode @Cached EqualsNode equalsNode
@ -530,7 +525,7 @@ public abstract class EqualsNode extends Node {
for (long i = 0; i < selfSize; i++) { for (long i = 0; i < selfSize; i++) {
Object selfElem = selfInterop.readArrayElement(selfArray, i); Object selfElem = selfInterop.readArrayElement(selfArray, i);
Object otherElem = otherInterop.readArrayElement(otherArray, i); Object otherElem = otherInterop.readArrayElement(otherArray, i);
Object elemsAreEqual; boolean elemsAreEqual;
if (selfElem instanceof Atom selfAtomElem if (selfElem instanceof Atom selfAtomElem
&& otherElem instanceof Atom otherAtomElem && otherElem instanceof Atom otherAtomElem
&& hasCustomComparatorNode.execute(selfAtomElem)) { && hasCustomComparatorNode.execute(selfAtomElem)) {
@ -538,7 +533,7 @@ public abstract class EqualsNode extends Node {
} else { } else {
elemsAreEqual = equalsNode.execute(selfElem, otherElem); elemsAreEqual = equalsNode.execute(selfElem, otherElem);
} }
if (!(elemsAreEqual instanceof Boolean elemsAreEqualBool && elemsAreEqualBool)) { if (!elemsAreEqual) {
return false; return false;
} }
} }
@ -573,8 +568,7 @@ public abstract class EqualsNode extends Node {
if (otherInterop.isHashEntryExisting(otherHashMap, key) if (otherInterop.isHashEntryExisting(otherHashMap, key)
&& otherInterop.isHashEntryReadable(otherHashMap, key)) { && otherInterop.isHashEntryReadable(otherHashMap, key)) {
Object otherValue = otherInterop.readHashValue(otherHashMap, key); Object otherValue = otherInterop.readHashValue(otherHashMap, key);
Object res = equalsNode.execute(selfValue, otherValue); if (!equalsNode.execute(selfValue, otherValue)) {
if (!(res instanceof Boolean resBool && resBool)) {
return false; return false;
} }
} else { } else {
@ -610,8 +604,7 @@ public abstract class EqualsNode extends Node {
for (int i = 0; i < membersSize; i++) { for (int i = 0; i < membersSize; i++) {
String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i)); String selfMemberName = interop.asString(interop.readArrayElement(selfMembers, i));
String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i)); String otherMemberName = interop.asString(interop.readArrayElement(otherMembers, i));
Object res = equalsNode.execute(selfMemberName, otherMemberName); if (!equalsNode.execute(selfMemberName, otherMemberName)) {
if (!(res instanceof Boolean resBool && resBool)) {
return false; return false;
} }
memberNames[i] = selfMemberName; memberNames[i] = selfMemberName;
@ -623,8 +616,7 @@ public abstract class EqualsNode extends Node {
interop.isMemberReadable(otherObject, memberNames[i])) { interop.isMemberReadable(otherObject, memberNames[i])) {
Object selfMember = interop.readMember(selfObject, memberNames[i]); Object selfMember = interop.readMember(selfObject, memberNames[i]);
Object otherMember = interop.readMember(otherObject, memberNames[i]); Object otherMember = interop.readMember(otherObject, memberNames[i]);
Object res = equalsNode.execute(selfMember, otherMember); if (!equalsNode.execute(selfMember, otherMember)) {
if (!(res instanceof Boolean resBool && resBool)) {
return false; return false;
} }
} }
@ -646,11 +638,6 @@ public abstract class EqualsNode extends Node {
return selfConstructor == otherConstructor; 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) { static EqualsNode[] createEqualsNodes(int size) {
EqualsNode[] nodes = new EqualsNode[size]; EqualsNode[] nodes = new EqualsNode[size];
Arrays.fill(nodes, EqualsNode.build()); Arrays.fill(nodes, EqualsNode.build());
@ -693,15 +680,10 @@ public abstract class EqualsNode extends Node {
// custom comparators. EqualsNode cannot deal with custom comparators. // custom comparators. EqualsNode cannot deal with custom comparators.
fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField); fieldsAreEqual = invokeAnyEqualsNode.execute(selfAtomField, otherAtomField);
} else { } else {
Object res = fieldEqualsNodes[i].execute( fieldsAreEqual = fieldEqualsNodes[i].execute(
selfFields[i], selfFields[i],
otherFields[i] otherFields[i]
); );
if (res instanceof Boolean resBool) {
fieldsAreEqual = resBool;
} else {
return false;
}
} }
if (!fieldsAreEqual) { if (!fieldsAreEqual) {
return false; return false;
@ -728,12 +710,7 @@ public abstract class EqualsNode extends Node {
&& HasCustomComparatorNode.getUncached().execute(selfFieldAtom)) { && HasCustomComparatorNode.getUncached().execute(selfFieldAtom)) {
areFieldsSame = InvokeAnyEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom); areFieldsSame = InvokeAnyEqualsNode.getUncached().execute(selfFieldAtom, otherFieldAtom);
} else { } else {
Object res = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]); areFieldsSame = EqualsNodeGen.getUncached().execute(selfFields[i], otherFields[i]);
if (res instanceof Boolean resBool) {
areFieldsSame = resBool;
} else {
return false;
}
} }
if (!areFieldsSame) { if (!areFieldsSame) {
return false; return false;
@ -766,7 +743,7 @@ public abstract class EqualsNode extends Node {
"isHostFunction(selfHostFunc)", "isHostFunction(selfHostFunc)",
"isHostFunction(otherHostFunc)" "isHostFunction(otherHostFunc)"
}) })
Object equalsHostFunctions(Object selfHostFunc, Object otherHostFunc, boolean equalsHostFunctions(Object selfHostFunc, Object otherHostFunc,
@CachedLibrary(limit = "5") InteropLibrary interop, @CachedLibrary(limit = "5") InteropLibrary interop,
@Cached EqualsNode equalsNode) { @Cached EqualsNode equalsNode) {
Object selfFuncStrRepr = interop.toDisplayString(selfHostFunc); 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, * 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. * for example, not Integer, as that cannot be present in Enso.
* All the primitive types should be handled in their corresponding specializations. * 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 @TruffleBoundary
boolean isHostObject(Object object) { boolean isHostObject(Object object) {
return EnsoContext.get(this).getEnvironment().isHostObject(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; import org.enso.interpreter.node.expression.builtin.Builtin;
@BuiltinType @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 * 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 * than the default (internal) ones.
* Default_Unordered_Comparator} and {@code Default_Ordered_Comparator}.
* *
* @param atom Atom for which we check whether it has custom comparator * @param atom Atom for which we check whether it has custom comparator
* @return true iff the given atom has a 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", type = "Comparable",
name = "less_than_builtin", name = "less_than_builtin",
description = """ 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. not comparable.
""" """
) )
@ -328,11 +328,6 @@ public abstract class LessThanNode extends Node {
return nothing(); 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() { private Object nothing() {
return EnsoContext.get(this).getNothing(); 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.mutable.Ref;
import org.enso.interpreter.node.expression.builtin.immutable.Vector; 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.Comparable;
import org.enso.interpreter.node.expression.builtin.ordering.DefaultOrderedComparator; import org.enso.interpreter.node.expression.builtin.ordering.DefaultComparator;
import org.enso.interpreter.node.expression.builtin.ordering.DefaultUnorderedComparator;
import org.enso.interpreter.node.expression.builtin.ordering.Ordering; 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.resource.ManagedResource;
import org.enso.interpreter.node.expression.builtin.text.Text; import org.enso.interpreter.node.expression.builtin.text.Text;
@ -86,8 +85,7 @@ public class Builtins {
private final Boolean bool; private final Boolean bool;
private final Ordering ordering; private final Ordering ordering;
private final Comparable comparable; private final Comparable comparable;
private final DefaultOrderedComparator defaultOrderedComparator; private final DefaultComparator defaultComparator;
private final DefaultUnorderedComparator defaultUnorderedComparator;
private final System system; private final System system;
private final Special special; private final Special special;
@ -135,8 +133,7 @@ public class Builtins {
error = new Error(this, context); error = new Error(this, context);
ordering = getBuiltinType(Ordering.class); ordering = getBuiltinType(Ordering.class);
comparable = getBuiltinType(Comparable.class); comparable = getBuiltinType(Comparable.class);
defaultUnorderedComparator = getBuiltinType(DefaultUnorderedComparator.class); defaultComparator = getBuiltinType(DefaultComparator.class);
defaultOrderedComparator = getBuiltinType(DefaultOrderedComparator.class);
system = new System(this); system = new System(this);
number = new Number(this); number = new Number(this);
bool = this.getBuiltinType(Boolean.class); bool = this.getBuiltinType(Boolean.class);
@ -598,12 +595,8 @@ public class Builtins {
return comparable; return comparable;
} }
public DefaultOrderedComparator defaultOrderedComparator() { public DefaultComparator defaultComparator() {
return defaultOrderedComparator; return defaultComparator;
}
public DefaultUnorderedComparator defaultUnorderedComparator() {
return defaultUnorderedComparator;
} }
/** @return the container for the dataflow error-related builtins */ /** @return the container for the dataflow error-related builtins */

View File

@ -1,7 +1,6 @@
package org.enso.interpreter.test; package org.enso.interpreter.test;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import java.time.LocalDate; 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.interop.syntax.HostValueToEnsoNode;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNode; import org.enso.interpreter.node.expression.builtin.meta.EqualsNode;
import org.enso.interpreter.node.expression.builtin.meta.EqualsNodeGen; 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.Context;
import org.graalvm.polyglot.Value; import org.graalvm.polyglot.Value;
import org.junit.AfterClass; import org.junit.AfterClass;
@ -92,17 +89,9 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
Object firstResult = equalsNode.execute(firstValue, secondValue); boolean firstResult = equalsNode.execute(firstValue, secondValue);
Object secondResult = equalsNode.execute(secondValue, firstValue); boolean secondResult = equalsNode.execute(secondValue, firstValue);
if (firstResult instanceof Boolean firstResultBool && secondResult instanceof Boolean secondResultBool) { assertEquals("equals should be symmetric", firstResult, secondResult);
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
);
}
return null; return null;
}); });
} }
@ -146,7 +135,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue((Boolean) equalsNode.execute(ensoDate, javaDate)); assertTrue(equalsNode.execute(ensoDate, javaDate));
return null; return null;
}); });
} }
@ -164,7 +153,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue((Boolean) equalsNode.execute(ensoTime, javaDate)); assertTrue(equalsNode.execute(ensoTime, javaDate));
return null; return null;
}); });
} }
@ -187,7 +176,7 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue((Boolean) equalsNode.execute(ensoDateTime, javaDateTime)); assertTrue(equalsNode.execute(ensoDateTime, javaDateTime));
return null; return null;
}); });
} }
@ -200,26 +189,8 @@ public class EqualsTest extends TestBase {
executeInContext( executeInContext(
context, context,
() -> { () -> {
assertTrue((Boolean) equalsNode.execute(ensoVector, javaVector)); assertTrue(equalsNode.execute(ensoVector, javaVector));
return null; 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 Data x
type My_Comparator type My_Comparator
is_ordered = True
compare my_1 my_2 = compare my_1 my_2 =
comparator = Comparable.from my_2.x comparator = Comparable.from my_2.x
comparator.compare my_2.x my_1.x comparator.compare my_2.x my_1.x

View File

@ -16,8 +16,6 @@ type My_Type
Value x y Value x y
type My_Type_Comparator type My_Type_Comparator
is_ordered = True
compare my_1 my_2 = compare my_1 my_2 =
comp = Comparable.from my_1.x comp = Comparable.from my_1.x
comp.compare (my_1.x + my_1.y) (my_2.x + my_2.y) 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 My.Data x1 y1 -> My.Data y1 x1
type My_Comparator type My_Comparator
is_ordered = True
compare left right = compare left right =
left_sum = left.x + left.y left_sum = left.x + left.y
right_sum = right.x + right.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.Error.No_Such_Key.No_Such_Key
import Standard.Base.Data.Time.Date_Time.Date_Time import Standard.Base.Data.Time.Date_Time.Date_Time
from Standard.Base.Data.Map import Map from Standard.Base.Data.Map import Map
from Standard.Base.Data.Ordering import Default_Unordered_Comparator
from Standard.Test import Test, Test_Suite, Problems from Standard.Test import Test, Test_Suite, Problems
import Standard.Test.Extensions import Standard.Test.Extensions
@ -76,10 +75,10 @@ spec =
empty_map.is_empty . should_be_true empty_map.is_empty . should_be_true
non_empty.is_empty . should_be_false non_empty.is_empty . should_be_false
Test.specify "should get default unordered comparator for polyglot maps" <| Test.specify "should get the default comparator for polyglot maps" <|
Comparable.from (Map.empty) . should_equal Default_Unordered_Comparator Comparable.from (Map.empty) . should_equal Default_Comparator
Comparable.from (js_empty_dict) . should_equal Default_Unordered_Comparator Comparable.from (js_empty_dict) . should_equal Default_Comparator
Comparable.from (JavaMap.of "A" 1 "B" 2) . should_equal Default_Unordered_Comparator Comparable.from (JavaMap.of "A" 1 "B" 2) . should_equal Default_Comparator
Test.specify "should compare two hash maps" <| Test.specify "should compare two hash maps" <|
(Map.singleton "a" 1).should_equal (Map.singleton "a" 1) (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 >= hundred_factorial).should_be_false
(very_negative >= Nothing).should_fail_with Type_Error (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 2 . should_equal Ordering.Less
Ordering.compare 1 1 . should_equal Ordering.Equal Ordering.compare 1 1 . should_equal Ordering.Equal
Ordering.compare 1 0 . should_equal Ordering.Greater 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`. # The comparison is reverted, i.e., `x < y` gives result for `y.number < x.number`.
type Ord_Comparator type Ord_Comparator
is_ordered = True
compare x y = (Comparable.from y.number) . compare y.number x.number compare x y = (Comparable.from y.number) . compare y.number x.number
hash x = (Comparable.from x.number) . hash x.number hash x = (Comparable.from x.number) . hash x.number
@ -24,7 +23,11 @@ Comparable.from (_:Ord) = Ord_Comparator
type No_Ord type No_Ord
Value number 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 # Tests
@ -67,7 +70,7 @@ spec = Test.group "Object Comparator" <|
((default_comparator (Ord.Value 1) (Ord.Value 1)) == 0) . should_equal True ((default_comparator (Ord.Value 1) (Ord.Value 1)) == 0) . should_equal True
Test.specify "should fail gracefully for incomparable items" <| 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 (default_comparator (No_Ord.Value 1) (No_Ord.Value 2)).should_fail_with Incomparable_Values
main = Test_Suite.run_main spec main = Test_Suite.run_main spec

View File

@ -11,7 +11,6 @@ type Ord
Value number Value number
type Ord_Comparator type Ord_Comparator
is_ordered = True
compare x y = (Comparable.from x.number) . compare x.number y.number compare x y = (Comparable.from x.number) . compare x.number y.number
hash x = (Comparable.from x.number) . hash x.number hash x = (Comparable.from x.number) . hash x.number
@ -22,12 +21,10 @@ type UPair
Value x y Value x y
type UPair_Comparator type UPair_Comparator
is_ordered = False compare pair1 pair2 =
if pair1.x == pair2.x && pair1.y == pair2.y then Ordering.Equal else
equals pair1 pair2 = if pair1.x == pair2.y && pair1.y == pair2.x then Ordering.Equal else
if pair1.x == pair2.x && pair1.y == pair2.y then True else Nothing
if pair1.x == pair2.y && pair1.y == pair2.x then True else
False
hash upair = hash upair =
x_comp = Comparable.from upair.x x_comp = Comparable.from upair.x
@ -42,24 +39,17 @@ type Parent
# === The Tests === # === The Tests ===
spec = spec =
Test.group "Default ordered comparator" <| Test.group "Default comparator" <|
Test.specify "should support custom ordered 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 2) . should_equal Ordering.Less
Ordering.compare (Ord.Value 1) (Ord.Value 1) . should_equal Ordering.Equal 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 (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 1))) . should_be_true
((Parent.Value (Ord.Value 1)) == (Parent.Value (Ord.Value 22))) . should_be_false ((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" <| 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 (UPair.Value 1 2) (Ord.Value 2) . should_fail_with Type_Error
Ordering.compare 1 Nothing . should_fail_with Type_Error Ordering.compare 1 Nothing . should_fail_with Type_Error

View File

@ -12,7 +12,6 @@ type Ord
Value number Value number
type Ord_Comparator type Ord_Comparator
is_ordered = True
compare x y = (Comparable.from x.number) . compare x.number y.number compare x y = (Comparable.from x.number) . compare x.number y.number
hash x = (Comparable.from x.number) . hash x.number hash x = (Comparable.from x.number) . hash x.number
@ -21,7 +20,11 @@ Comparable.from (_:Ord) = Ord_Comparator
type No_Ord type No_Ord
Value number 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 # 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.Regex_2.Regex_Syntax_Error
import Standard.Base.Data.Text.Span.Span import Standard.Base.Data.Text.Span.Span
import Standard.Base.Error.Common.Index_Out_Of_Bounds 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.Common.Type_Error
import Standard.Base.Error.Illegal_Argument.Illegal_Argument import Standard.Base.Error.Illegal_Argument.Illegal_Argument
@ -120,8 +121,8 @@ spec =
# Handling of Nothing # Handling of Nothing
(accent_1 == Nothing) . should_be_false (accent_1 == Nothing) . should_be_false
(accent_1 != Nothing) . should_be_true (accent_1 != Nothing) . should_be_true
Ordering.compare accent_1 Nothing . should_fail_with Type_Error Ordering.compare accent_1 Nothing . should_fail_with Incomparable_Values
(accent_1 > Nothing) . should_fail_with Type_Error (accent_1 > Nothing) . should_fail_with Incomparable_Values
accent_1 . compare_to_ignore_case Nothing . should_fail_with Type_Error accent_1 . compare_to_ignore_case Nothing . should_fail_with Type_Error
earlier_suffix = "aooooz" earlier_suffix = "aooooz"

View File

@ -123,9 +123,6 @@ spec_with name create_new_date parse_date =
result -> result ->
Test.fail ("Unexpected result: " + result.to_text) 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" <| Test.specify "should be comparable" <|
date_1 = parse_date "2021-01-02" date_1 = parse_date "2021-01-02"
date_2 = parse_date "2021-01-01" 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 . nanosecond . should_equal 0
time . zone . zone_id . should_equal Time_Zone.utc.zone_id time . zone . zone_id . should_equal Time_Zone.utc.zone_id
Test.specify "should get default ordered comparator for datetimes" <| Test.specify "should get the default comparator for datetimes" <|
Comparable.from (create_new_datetime 2023 2 3 23 59) . should_equal Default_Ordered_Comparator 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_Ordered_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_Ordered_Comparator Comparable.from (create_new_datetime 2022 10 31 2 30 55 1234) . should_equal Default_Comparator
Test.specify "should be comparable" <| Test.specify "should be comparable" <|
time_1 = parse_datetime "2021-01-01T00:30:12.7102[UTC]" time_1 = parse_datetime "2021-01-01T00:30:12.7102[UTC]"

View File

@ -1,5 +1,5 @@
from Standard.Base import all 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 import Standard.Base.Error.Time_Error.Time_Error
from Standard.Test import Test, Test_Suite from Standard.Test import Test, Test_Suite
@ -62,8 +62,8 @@ spec =
(duration - period).should_fail_with Time_Error (duration - period).should_fail_with Time_Error
(period + duration).should_fail_with Time_Error (period + duration).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 Incomparable_Values
(duration < period).should_fail_with Type_Error (duration < period).should_fail_with Incomparable_Values
Test.specify "Date_Time supports adding and subtracting Duration" <| 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) ((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 Value a b
type T_Comparator type T_Comparator
is_ordered = True
compare t1 t2 = Comparable.from t1.a . compare t1.a t2.a compare t1 t2 = Comparable.from t1.a . compare t1.a t2.a
hash t = Comparable.from t.a . hash t.a hash t = Comparable.from t.a . hash t.a

View File

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