mirror of
https://github.com/enso-org/enso.git
synced 2024-12-23 10:21:41 +03:00
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:
parent
a7f1ba96a9
commit
5f7a4a5a39
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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);
|
||||
|
@ -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 {}
|
@ -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 {}
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 */
|
||||
|
@ -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;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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]"
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user