diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc index e7dfc2ff71..bb90ca245f 100644 --- a/crates/compiler/builtins/roc/Bool.roc +++ b/crates/compiler/builtins/roc/Bool.roc @@ -1,5 +1,5 @@ interface Bool - exposes [Bool, true, false, and, or, not, isEq, isNotEq] + exposes [Bool, true, false, and, or, not, isNotEq] imports [] Bool := [True, False] @@ -67,21 +67,6 @@ or : Bool, Bool -> Bool ## Returns `Bool.false` when given `Bool.true`, and vice versa. not : Bool -> Bool -## Returns `Bool.true` if the two values are *structurally equal*, and `Bool.false` otherwise. -## -## `a == b` is shorthand for `Bool.isEq a b` -## -## Structural equality works as follows: -## -## 1. Tags are equal if they have the same tag name, and also their contents (if any) are equal. -## 2. Records are equal if all their fields are equal. -## 3. Collections ([Str], [List], [Dict], and [Set]) are equal if they are the same length, and also all their corresponding elements are equal. -## 4. [Num](Num#Num) values are equal if their numbers are equal, with one exception: if both arguments to `isEq` are *NaN*, then `isEq` returns `Bool.false`. See `Num.isNaN` for more about *NaN*. -## -## Note that `isEq` takes `'val` instead of `val`, which means `isEq` does not -## accept arguments whose types contain functions. -isEq : a, a -> Bool - ## Calls [isEq] on the given values, then calls [not] on the result. ## ## `a != b` is shorthand for `Bool.isNotEq a b` diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc index 2400cc3121..2f496b6f8d 100644 --- a/crates/compiler/builtins/roc/Dict.roc +++ b/crates/compiler/builtins/roc/Dict.roc @@ -19,6 +19,7 @@ interface Dict ] imports [ Bool.{ Bool }, + Eq.{ Eq }, Result.{ Result }, List, Num.{ Nat }, @@ -72,7 +73,9 @@ interface Dict ## When comparing two dictionaries for equality, they are `==` only if their both their contents and their ## orderings match. This preserves the property that if `dict1 == dict2`, you should be able to rely on ## `fn dict1 == fn dict2` also being `Bool.true`, even if `fn` relies on the dictionary's ordering. -Dict k v := List [Pair k v] +Dict k v := List [Pair k v] has [Eq { isEq: dictEq }] + +dictEq = \@Dict l1, @Dict l2 -> l1 == l2 ## An empty dictionary. empty : Dict k v @@ -81,7 +84,7 @@ empty = @Dict [] withCapacity : Nat -> Dict k v withCapacity = \n -> @Dict (List.withCapacity n) -get : Dict k v, k -> Result v [KeyNotFound]* +get : Dict k v, k -> Result v [KeyNotFound]* | k has Eq get = \@Dict list, needle -> when List.findFirst list (\Pair key _ -> key == needle) is Ok (Pair _ v) -> @@ -94,7 +97,7 @@ walk : Dict k v, state, (state, k, v -> state) -> state walk = \@Dict list, initialState, transform -> List.walk list initialState (\state, Pair k v -> transform state k v) -insert : Dict k v, k, v -> Dict k v +insert : Dict k v, k, v -> Dict k v | k has Eq insert = \@Dict list, k, v -> when List.findFirstIndex list (\Pair key _ -> key == k) is Err NotFound -> @@ -109,7 +112,7 @@ len : Dict k v -> Nat len = \@Dict list -> List.len list -remove : Dict k v, k -> Dict k v +remove : Dict k v, k -> Dict k v | k has Eq remove = \@Dict list, key -> when List.findFirstIndex list (\Pair k _ -> k == key) is Err NotFound -> @@ -128,7 +131,7 @@ update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) - -> Dict k v + -> Dict k v | k has Eq update = \dict, key, alter -> possibleValue = get dict key @@ -151,7 +154,7 @@ expect update empty "a" alterValue == single "a" Bool.false expect update (single "a" Bool.false) "a" alterValue == single "a" Bool.true expect update (single "a" Bool.true) "a" alterValue == empty -contains : Dict k v, k -> Bool +contains : Dict k v, k -> Bool | k has Eq contains = \@Dict list, needle -> step = \_, Pair key _val -> if key == needle then @@ -178,18 +181,18 @@ values = \@Dict list -> List.map list (\Pair _ v -> v) # union : Dict k v, Dict k v -> Dict k v -insertAll : Dict k v, Dict k v -> Dict k v +insertAll : Dict k v, Dict k v -> Dict k v | k has Eq insertAll = \xs, @Dict ys -> List.walk ys xs (\state, Pair k v -> Dict.insertIfVacant state k v) # intersection : Dict k v, Dict k v -> Dict k v -keepShared : Dict k v, Dict k v -> Dict k v +keepShared : Dict k v, Dict k v -> Dict k v | k has Eq keepShared = \@Dict xs, ys -> List.keepIf xs (\Pair k _ -> Dict.contains ys k) |> @Dict # difference : Dict k v, Dict k v -> Dict k v -removeAll : Dict k v, Dict k v -> Dict k v +removeAll : Dict k v, Dict k v -> Dict k v | k has Eq removeAll = \xs, @Dict ys -> List.walk ys xs (\state, Pair k _ -> Dict.remove state k) @@ -202,7 +205,7 @@ insertFresh = \@Dict list, k, v -> |> List.append (Pair k v) |> @Dict -insertIfVacant : Dict k v, k, v -> Dict k v +insertIfVacant : Dict k v, k, v -> Dict k v | k has Eq insertIfVacant = \dict, key, value -> if Dict.contains dict key then dict diff --git a/crates/compiler/builtins/roc/Eq.roc b/crates/compiler/builtins/roc/Eq.roc index 15e5011d03..ceba050852 100644 --- a/crates/compiler/builtins/roc/Eq.roc +++ b/crates/compiler/builtins/roc/Eq.roc @@ -6,7 +6,7 @@ interface Eq structuralEq, ] imports [ - Bool, + Bool.{ Bool }, ] ## A type that can be compared for total equality. diff --git a/crates/compiler/builtins/roc/Json.roc b/crates/compiler/builtins/roc/Json.roc index ed39e33819..e4bbbb4e9d 100644 --- a/crates/compiler/builtins/roc/Json.roc +++ b/crates/compiler/builtins/roc/Json.roc @@ -34,6 +34,7 @@ interface Json Dec, }, Bool.{ Bool }, + Eq.{ Eq }, Result, ] diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc index 7d5b90d40e..f4afdca982 100644 --- a/crates/compiler/builtins/roc/List.roc +++ b/crates/compiler/builtins/roc/List.roc @@ -66,6 +66,7 @@ interface List ] imports [ Bool.{ Bool }, + Eq.{ Eq }, Result.{ Result }, Num.{ Nat, Num, Int }, ] @@ -354,7 +355,7 @@ join = \lists -> List.walk lists (List.withCapacity totalLength) (\state, list -> List.concat state list) -contains : List a, a -> Bool +contains : List a, a -> Bool | a has Eq contains = \list, needle -> List.any list (\x -> x == needle) @@ -903,7 +904,7 @@ intersperse = \list, sep -> ## is considered to "start with" an empty list. ## ## If the first list is empty, this only returns `Bool.true` if the second list is empty. -startsWith : List elem, List elem -> Bool +startsWith : List elem, List elem -> Bool | elem has Eq startsWith = \list, prefix -> # TODO once we have seamless slices, verify that this wouldn't # have better performance with a function like List.compareSublists @@ -915,7 +916,7 @@ startsWith = \list, prefix -> ## is considered to "end with" an empty list. ## ## If the first list is empty, this only returns `Bool.true` if the second list is empty. -endsWith : List elem, List elem -> Bool +endsWith : List elem, List elem -> Bool | elem has Eq endsWith = \list, suffix -> # TODO once we have seamless slices, verify that this wouldn't # have better performance with a function like List.compareSublists @@ -944,7 +945,7 @@ split = \elements, userSplitIndex -> ## remaining elements after that occurrence. If the delimiter is not found, returns `Err`. ## ## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] } -splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* +splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq splitFirst = \list, delimiter -> when List.findFirstIndex list (\elem -> elem == delimiter) is Ok index -> @@ -959,7 +960,7 @@ splitFirst = \list, delimiter -> ## remaining elements after that occurrence. If the delimiter is not found, returns `Err`. ## ## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] } -splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* +splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound]* | elem has Eq splitLast = \list, delimiter -> when List.findLastIndex list (\elem -> elem == delimiter) is Ok index -> diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc index a5f1d087a5..e8d284c66b 100644 --- a/crates/compiler/builtins/roc/Num.roc +++ b/crates/compiler/builtins/roc/Num.roc @@ -146,6 +146,7 @@ interface Num imports [ Bool.{ Bool }, Result.{ Result }, + Eq, ] ## Represents a number that could be either an [Int] or a [Frac]. @@ -793,7 +794,7 @@ div : Frac a, Frac a -> Frac a divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]* divChecked = \a, b -> - if b == 0 then + if Num.isZero b then Err DivByZero else Ok (Num.div a b) @@ -802,7 +803,7 @@ divCeil : Int a, Int a -> Int a divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]* divCeilChecked = \a, b -> - if b == 0 then + if Num.isZero b then Err DivByZero else Ok (Num.divCeil a b) @@ -827,7 +828,7 @@ divTrunc : Int a, Int a -> Int a divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]* divTruncChecked = \a, b -> - if b == 0 then + if Num.isZero b then Err DivByZero else Ok (Num.divTrunc a b) @@ -847,7 +848,7 @@ rem : Int a, Int a -> Int a remChecked : Int a, Int a -> Result (Int a) [DivByZero]* remChecked = \a, b -> - if b == 0 then + if Num.isZero b then Err DivByZero else Ok (Num.rem a b) diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc index c1fe50a5ad..d9b9305813 100644 --- a/crates/compiler/builtins/roc/Set.roc +++ b/crates/compiler/builtins/roc/Set.roc @@ -14,9 +14,11 @@ interface Set intersection, difference, ] - imports [List, Bool.{ Bool }, Dict.{ Dict }, Num.{ Nat }] + imports [List, Bool.{ Bool }, Eq.{ Eq }, Dict.{ Dict }, Num.{ Nat }] -Set k := Dict.Dict k {} +Set k := Dict.Dict k {} has [Eq { isEq: setEq }] + +setEq = \@Set d1, @Set d2 -> d1 == d2 fromDict : Dict k {} -> Set k fromDict = \dict -> @Set dict @@ -35,7 +37,7 @@ single = \key -> ## Make sure never to insert a *NaN* to a [Set]! Because *NaN* is defined to be ## unequal to *NaN*, adding a *NaN* results in an entry that can never be ## retrieved or removed from the [Set]. -insert : Set k, k -> Set k +insert : Set k, k -> Set k | k has Eq insert = \@Set dict, key -> dict |> Dict.insert key {} @@ -75,11 +77,11 @@ expect actual == 3 ## Drops the given element from the set. -remove : Set k, k -> Set k +remove : Set k, k -> Set k | k has Eq remove = \@Set dict, key -> @Set (Dict.remove dict key) -contains : Set k, k -> Bool +contains : Set k, k -> Bool | k has Eq contains = \set, key -> set |> Set.toDict @@ -89,21 +91,21 @@ toList : Set k -> List k toList = \@Set dict -> Dict.keys dict -fromList : List k -> Set k +fromList : List k -> Set k | k has Eq fromList = \list -> initial = @Set (Dict.withCapacity (List.len list)) List.walk list initial \set, key -> Set.insert set key -union : Set k, Set k -> Set k +union : Set k, Set k -> Set k | k has Eq union = \@Set dict1, @Set dict2 -> @Set (Dict.insertAll dict1 dict2) -intersection : Set k, Set k -> Set k +intersection : Set k, Set k -> Set k | k has Eq intersection = \@Set dict1, @Set dict2 -> @Set (Dict.keepShared dict1 dict2) -difference : Set k, Set k -> Set k +difference : Set k, Set k -> Set k | k has Eq difference = \@Set dict1, @Set dict2 -> @Set (Dict.removeAll dict1 dict2) diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc index 3e10116b05..0fe718a830 100644 --- a/crates/compiler/builtins/roc/Str.roc +++ b/crates/compiler/builtins/roc/Str.roc @@ -48,6 +48,7 @@ interface Str ] imports [ Bool.{ Bool }, + Eq.{ Eq }, Result.{ Result }, List, Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec }, diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs index 9a46592f0a..efb0e4ae3c 100644 --- a/crates/compiler/can/src/operator.rs +++ b/crates/compiler/can/src/operator.rs @@ -409,7 +409,7 @@ fn binop_to_function(binop: BinOp) -> (&'static str, &'static str) { Percent => (ModuleName::NUM, "rem"), Plus => (ModuleName::NUM, "add"), Minus => (ModuleName::NUM, "sub"), - Equals => (ModuleName::BOOL, "isEq"), + Equals => (ModuleName::EQ, "isEq"), NotEquals => (ModuleName::BOOL, "isNotEq"), LessThan => (ModuleName::NUM, "isLt"), GreaterThan => (ModuleName::NUM, "isGt"),