Module /fusion/equality
Equality and identity procedures.
Equality Procedures
The definition of "equality" between values often depends upon the application, so it's not possible for Fusion to provide operators that will be sufficient for all circumstances. With that in mind, this module provides a spectrum of equality relations that should be sufficient for the majority of use cases.
The standard equality procedures, from most lenient to most strict, are briefly described here; see the reference below for full details. As a mnemonic, observe that longer names mean stricter comparisons.
=Content equivalence. Recursively compares containers. Ignores annotations and numeric precision. May coerce values to a common abstract supertype; for example, sequences with
=content are themselves=.Use
=when you care about shape and content, but not type.==Structural equivalence. Like
=but does not coerce values to a common abstract supertype.Use
==when you care about type and content.===Strict structural equivalence. Like
==but compares annotations and numeric precision.Use
===when you care about the data's (default) ionized form.sameAbstract identity equivalence. Compares object references, but treats (unannotated) numbers as if they were interned.
Use
samewhen you care about identity and may encounter numbers.identConcrete identity equivalence. Not sound for numbers due to potential optimization effects.
Use
identfor best performance when you care about identity and are not using numbers.
Note that these operations define "nested" equivalence classes:
- If
(ident a b)is true then(same a b)is true. - If
(same a b)is true then(=== a b)is true. - If
(=== a b)is true then(== a b)is true. - If
(== a b)is true then(= a b)is true.
Here's a rough comparison to other languages:
Fusion Scheme Ruby Python Dylan Java
--------------------------------------------------------
= == =
== equal? eql? ==
=== equals()
same eqv? ==
ident eq? equal? is ==
Exported Bindings
(= left right)
Returns true if the arguments are equivalent in shape and content, possibly by coercing values to a common type. Annotations and precision are ignored.
- Any value is
=to itself. - Nulls of any type are
=to each other. - Bools are
=in the obvious way. - Numbers are
=when they represent the same numeric value, without regard to precision or negative zeros. When a float is compared to an int or decimal, it is coerced to a decimal. - Timestamps are
=when they represent the same point-in-time, without regard to precision or local offset. - Strings and symbols are
=(interchangably) when they contain the same sequence of code points. - Blobs and clobs are
=(interchangably) when they contain the same sequence of bytes. - Pairs are
=when their heads and tails are=. - Sequences are
=when they have the same size, and elements at the same index are=. - Structs are
=when they have the same set of field names, and each name maps to a set of elements that are=. - Void is
=only to itself. - Exceptions are not thrown due to mismatched types; instead the result is
false.
For example:
(= null (quote a::null)) --> true
(= null null.clob) --> true
(= 1 1.00) --> true
(= 0 -0e-3) --> true
(= 2014T 2014-01-01T02:00+02:00) --> true
(= 2014T 2014) --> false
(= "text" (quote text)) --> true
(= "text" (quote a::"text")) --> true
(= [1, 2] (sexp 1 2.00)) --> true
(= null.list []) --> false
(= (struct "f" 1) (mutable_struct "f" 1.0)) --> true
(= {f:1, f:1} {f:1}) --> false
At present, the coercion of float to decimal is precise, without rounding to approximate a prettier decimal form. This may lead to strange behavior since most decimal numbers don't have a precise binary representation:
(= 1.2 1.2e0) --> false
(= 1.5 1.5e0) --> true
It's possible this may change in a future release.
Warning: The behavior of this procedure is undefined for values +inf,
-inf, and nan.
See issue #64.
(== left right)
Like = but does not coerce values to a common abstract supertype.
Annotations and precision are ignored.
- Any value is
==to itself. - Nulls are only
==to nulls of the same type. - Numbers are
==when they have the same type and numeric value, without regard to precision or negative zeros. - Strings are only
==to strings, and symbols to symbols. - Blobs are only
==to blobs, and clobs to clobs. - Pairs are
==when their heads and tails are==. - Lists (and sexps) are
==when they have the same type and size, and elements at the same index are==. - Structs are
==when they have the same set of field names, and each name maps to a set of elements that are==.
For example:
(== null (quote a::null)) --> true
(== null null.clob) --> false
(== 1 1.) --> false
(== 1. 1.0) --> true
(=== 0. -0.) --> true
(== 2014T 2014-01-01T02:00+02:00) --> true
(== 2014T 2014) --> false
(== "text" (quote text)) --> false
(== "text" (quote a::"text")) --> true
(== [1, 2] (sexp 1 2 )) --> false
(== [1, 2] (list 1 2.00)) --> false
(== [1, 2] (list 1 2 )) --> true
(== [1, 2] (mutable_list 1 2)) --> true
Warning: The behavior of this procedure is undefined for values +inf,
-inf, and nan.
See issue #64.
(=== left right)
Like == but annotations and precision are not ignored.
- Values are
===only when their annotations are equal. - Decimals are
===only when they have the same numeric value and precision. Negative zeros are not===to positive zeros. - Timestamps are
===only when they represent the same point-in-time and have the same precision and local offset.
For example:
(=== null (quote a::null)) --> false
(=== (quote a::null) (quote a::null)) --> true
(=== null null.clob) --> false
(=== 1 1.) --> false
(=== 1. 1.0) --> false
(=== 1.0 1.0) --> true
(=== 0. -0.) --> false
(=== 2014T 2014-01-01T02:00+02:00) --> false
(=== 2014-01-01T00:00+00:00
2014-01-01T00:00-00:00) --> false
(=== (quote a::1) (quote a::a::1)) --> false
(=== (quote b::a::1) (quote a::b::1)) --> false
Warning: The behavior of this procedure is undefined for values +inf,
-inf, and nan.
See issue #64.
(ident left right)
Returns true if and only if the arguments are the same object; that is, when they have the same object identity.
(ident 1 (quote a::1)) --> false
(let [(v "hi")] (ident v v)) --> true
(let [(v (quote a::"hi"))] (ident v v)) --> true
(ident (void) (void)) --> true
At the implementation level, ident is a trivial pointer comparison.
As such, it exposes some implementation details that may change over time or
across platforms.
Since Fusion symbols are interned, those with the same text and annotations are
always ident:
(ident (string_to_symbol "barn")
(quote barn)) --> true
(ident (string_to_symbol "barn")
(string_to_symbol
(string_append "ba" "rn"))) --> true
(ident (quote a::barn) (quote barn)) --> false
Fusion allows implementation optimizations that copy and/or intern numeric values as needed. Therefore there's no promise of stable object identity for numbers, and this operator may behave in unexpected and implementation-defined ways when applied to them.
(ident 2 (+ 1 1)) --> // unspecified
(let [(v 2)] (ident v v)) --> // unspecified
Other than symbols, it is not specified whether literals or quoted data are
interned, so there's no promise of identity across two different syntactic
instances of the same value:
(ident 10600439 10600439) --> // unspecified
(ident "hi" "hi") --> // unspecified
(ident (quote a::"hi") (quote a::"hi")) --> // unspecified
(same left right)
Like ident but sound for numbers. That is, this procedure guarantees
equivalence of (unannotated) numbers with the same type, value, and precision,
while ident does not. In effect, unannotated numbers behave as if
they were interned.
As with ident, values with different annotations are never same.
(same 1 (quote a::1)) --> false
(same 2 (+ 1 1)) --> true
(let [(v 2)] (same v v)) --> true
(same 10600439 10600439) --> true
(same (quote a::"hi") (quote a::"hi")) --> // unspecified