|
1 | 1 | # Equality
|
2 | 2 |
|
3 |
| -When testing for equality in Slosh, there are several functions that can be used. |
4 |
| -Consider invocations like |
5 |
| -```slosh |
6 |
| -(= 1 2) |
7 |
| -``` |
8 |
| -```slosh |
9 |
| -(== 1 2) |
10 |
| -``` |
11 |
| -```slosh |
12 |
| -(identical? 1 2) |
13 |
| -``` |
14 |
| -```slosh |
15 |
| -(assert-equal 1 2) |
16 |
| -``` |
17 |
| -these are translated to different opcodes and equality implementations as listed below |
18 |
| - |
19 |
| -- `equal?` |
20 |
| - |
21 |
| - - lenient |
22 |
| - - `state.rs` defines special forms object with key `equal` mapped to the name `equal?`. |
| 3 | +The most common way to test equality is with `=` |
| 4 | +For numeric equality (IEEE) use `==` |
| 5 | +For bytewise equality use `identical?` |
| 6 | + |
| 7 | +The behavior and names are based on Clojure's implementation. Read their docs here: https://clojure.org/guides/equality |
| 8 | + |
| 9 | +- `=` |
| 10 | + - `(= 2 0x2)` is `true` (comparing an int to a byte) |
| 11 | + - `(= 2 2.0)` is `false` (comparing an int to a float) |
| 12 | + - `(= NaN NaN)` is `false` |
| 13 | + - `state.rs` defines special forms object with key `equal` mapped to the name `=`. |
23 | 14 | - `compile.rs` matches on `env.specials().equal` and generates opcode `EQUAL`
|
24 | 15 | - `exec_loop.rs` maps opcode `EQUAL` to function `is_equal`
|
25 | 16 | - `vm.rs` `is_equal` converts each arg to a `Value` (in case it needs to be dereferenced) and calls `is_equal_pair`
|
26 | 17 | - `vm.rs` `is_equal_pair` does a complex test for equality
|
27 | 18 | - check if both args are Byte or Int and if so, converts both to `i64` with `Value::get_int` and compares with rust native `==`
|
28 | 19 | - check if both args are Byte or Int or Float and if so, converts both to `f64` with `Value::get_float`
|
29 | 20 | - then subtracts the second from the first and takes the absolute value and tests `diff == 0.0`
|
| 21 | +- `==` |
| 22 | + |
| 23 | + - `(== 1 1.0)` is `true` (comparing an int to a float) |
| 24 | + - `(== NaN NaN)` is `false` |
| 25 | + - returns true whenever `=` does, but also returns true for numbers that are numerically equal |
| 26 | + |
| 27 | + - when comparing two floats, converts two both to `f64` and compares with native f64 `==` |
| 28 | + - does not use F56::PartialEq implementation |
| 29 | + |
| 30 | + - `state.rs` defines special forms object with key `numeq` mapped to the name `==`. |
| 31 | + - `compile.rs` calls `compile_list` which calls `compile_special` which calls `compile_math` in `compile_math.rs` |
| 32 | + - `compile_math.rs` `compile_math` matches on `env.specials().numeq` and generates opcode `NUMEQ` |
| 33 | + - `exec_loop.rs` maps opcode `NUMEQ` to function `compare_numeric` and passes a comparator `|a,b| a == b` |
| 34 | + - `macros.rs` `compare_numeric` |
| 35 | + - checks if either argument is a `Float` and if so, converts both to `f64` with `get_primitive_float` macro and uses the comparator |
| 36 | + - checks if either argument is a `Int` and if so, converts both to `i64` with `get_primitive_int` macro and uses the comparator |
| 37 | + |
| 38 | +- `identical?` |
| 39 | + |
| 40 | + - uses `Value::PartialEq` implementation |
| 41 | + - `state.rs` defines special forms object with key `eq` mapped to the name `identical?`. |
| 42 | + - `compile.rs` matches on `env.specials().eq` and generates opcode `EQ` |
| 43 | + - `exec_loop.rs` maps opcode `EQ` to function `is_identical` |
| 44 | + - `vm.rs` `is_identical` converts each arg to a `Value` (in case it needs to be dereferenced) and compares `val1 == val2` which uses `Value::PartialEq` implementation |
30 | 45 |
|
31 | 46 | - `assert-equal`
|
32 | 47 |
|
33 | 48 | - based on `equal?`
|
34 | 49 | - is a macro defined in core.slosh which checks if the arguments are `equal?` and throws an error if they are not
|
35 | 50 |
|
36 |
| -- `eq?` |
| 51 | +- `not=` |
| 52 | + - `(not= 1 2)` is equivalent to `(not (= 1 2))` |
| 53 | + - `(not= 1 1.0)` is `true` (comparing an int to a float) |
| 54 | + - `state.rs` defines special forms object with key `numneq` mapped to the name `not=`. |
| 55 | + - `compile_math.rs` converts `numneq` to opcode `NUMNEQ` |
| 56 | + - `exec_loop.rs` maps opcode `NUMNEQ` to a negation of the implementation of opcode `EQUAL` AKA `=` |
37 | 57 |
|
38 |
| - - uses `Value::PartialEq` implementation |
39 |
| - - `state.rs` defines special forms object with key `eq` mapped to the name `eq?`. |
40 |
| - - `compile.rs` matches on `env.specials().eq` and generates opcode `EQ` |
41 |
| - - `exec_loop.rs` maps opcode `EQ` to function `is_eq` |
42 |
| - - `vm.rs` `is_eq` converts each arg to a `Value` and compares `val1 == val2` which uses `Value::PartialEq` implementation |
| 58 | +### Moving Forward |
43 | 59 |
|
44 |
| -- `=` |
45 |
| - - numeric equality after converting to `i64` or `f64` |
46 |
| - - `state.rs` defines special forms object with key `numeq` mapped to the name `=`. |
47 |
| - - `compile.rs` calls `compile_list` which calls `compile_special` which calls `compile_math` in `compile_math.rs` |
48 |
| - - `compile_math.rs` `compile_math` matches on `env.specials().numeq` and generates opcode `NUMEQ` |
49 |
| - - `exec_loop.rs` maps opcode `NUMEQ` to function `compare_int` and passes two comparators |
50 |
| - - an integer comparator: `|a,b| a == b` |
51 |
| - - a float comparator: `|a: f64, b: f64| (a - b).abs() < f64::EPSILON` |
52 |
| - - `macros.rs` `compare_int` |
53 |
| - - checks if either argument is a `Float` and if so, converts both to `f64` with `get_float` macro and uses the float comparator |
54 |
| - - checks if either argument is a `Int` and if so, converts both to `i64` with `get_int` macro and uses the integer comparator |
55 |
| - - ultimately, ints are compared as i64 and floats are compared as f64 with a tolerance of `f64::EPSILON` |
| 60 | +- = should be converted to =? |
| 61 | +- =? should just be an alias for equal? which is the slower more intuitive equality check |
| 62 | +- eq? is the faster more strict equality check but it does spend a little extra time making sure to dereference captured heap values |
| 63 | +- eq? on floats should do an IEEE comparison |
| 64 | +- eq? should consider 1.0 equal to 1 |
| 65 | +- equal? on floats should do a fuzzy comparison |
| 66 | +- if we want a bitwise comparison of floats we will implement it later |
| 67 | + |
| 68 | +eq? does not do type coercion |
| 69 | +just do what clojure does |
| 70 | +numeq should be a bit fuzzy |
| 71 | +NaN != NaN is false and NaN == NaN is false |
| 72 | + |
| 73 | +may 1 2024 |
| 74 | += (equal) loosy goosey (still use IEEE comparison) |
| 75 | +== (=?) (for numbers) (IEEE comparison) |
| 76 | +identical? (eq?) (bytewise equality) |
| 77 | +users who want rough epsilon equality can defn their own fn |
0 commit comments