Ion Fusion Documentation
Release 0.38a1-SNAPSHOT (2026-04-16T19:45:37.790Z)

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.

Note that these operations define "nested" equivalence classes:

Here's a rough comparison to other languages:

    Fusion   Scheme   Ruby     Python   Dylan   Java
  --------------------------------------------------------
    =                 ==                =
    ==       equal?   eql?     ==
    ===                                         equals()
    same     eqv?                       ==
    ident    eq?      equal?   is               ==
= procedure
(= 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.

== procedure
(== 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.

=== procedure
(=== 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 procedure
(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 procedure
(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