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

Module /fusion/base

A reduced language that may be preferable to using the full /fusion language in some situations. In particular, this dialect may exhibit faster startup time since it requires fewer parts of the full library.

To use this dialect for your module, declare it as follows:

(module my_module "/fusion/base"
  // ...
)
* procedure
(* num ...)

Returns the product of the numbers, which must be int or decimal. With no arguments, returns integer 1.

+ procedure
(+ num ...)

Returns the sum of the numbers, which must be int or decimal. With no arguments, returns integer 0.

- procedure
(- num ...+)

With two or more int or decimal numbers, returns their difference, associating to the left. With one int or decimal argument, returns its negation.

/ procedure
(/ dividend divisor)

Returns a decimal whose numeric value is (dividend / divisor). Both arguments must be decimals. An exception is thrown if the result cannot be represented exactly.

< procedure
(< a b)

Returns true if a is less than b. Numbers are compared without regard to precision or negative zeros; if the values have different concrete types they are both coerced to decimal. Timestamps are compared to each other without regard to precision or local offset. Annotations are ignored.

Warning: The behavior of this procedure is undefined for values +inf, -inf, and nan. See issue #64.

<= procedure
(<= a b)

Returns true if a is less than or equal to b. Numbers are compared without regard to precision or negative zeros; if the values have different concrete types they are both coerced to decimal. Timestamps are compared to each other without regard to precision or local offset. Annotations are ignored.

Warning: The behavior of this procedure is undefined for values +inf, -inf, and nan. See issue #64.

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

> procedure
(> a b)

Returns true if a is greater than b. Numbers are compared without regard to precision or negative zeros; if the values have different concrete types they are both coerced to decimal. Timestamps are compared to each other without regard to precision or local offset. Annotations are ignored.

Warning: The behavior of this procedure is undefined for values +inf, -inf, and nan. See issue #64.

>= procedure
(>= a b)

Returns true if a is greater than or equal to b. Numbers are compared without regard to precision or negative zeros; if the values have different concrete types they are both coerced to decimal. Timestamps are compared to each other without regard to precision or local offset. Annotations are ignored.

Warning: The behavior of this procedure is undefined for values +inf, -inf, and nan. See issue #64.

all_defined_out syntax
(all_defined_out)

A provide clause that exports all bindings defined by the enclosing module. Imported bindings are not exported.

This form can only appear within provide.

and syntax
(and expr ...)

Evaluates the exprs from left to right, returning the first untruthy value that results (and ignoring further expressions). If they all return truthy values, the last one is returned.

The last expr is in tail position. Given no exprs, the result is true.

annotate procedure
(annotate value text ...)

Applies the given annotations to a value, returning a (shallow) copy if necessary to avoid mutating it. Any annotations on value are removed, so (annotate value) effectively de-annotates a value.

The value must be annotatable; that is, it must be one of the Ion types. The text arguments must be non-null strings or symbols.

(annotate 123 "a")              =>  a::123
(let [(v (quote a::123))]
  (annotate v))                 =>  123
(let [(anns ["a", (quote b)])]
  (apply annotate 123 anns))    =>  a::b::123

To extract annotations from a value, use annotations.

annotations procedure
(annotations value)

Returns a non-null immutable list of symbols containing the user type annotations on the value.

(annotations 123)                =>  []
(annotations (quote a::b::123))  =>  [a, b]

To put annotations onto a value, use annotate.

apply procedure
(apply proc arg ... sequence)

Calls the given proc with arguments that are the (optional) args prepended to the elements of sequence. The proc is called in tail position.

(apply + [1, 2])             =>  3
(apply + 10 11 (sexp 1 2))   =>  24
begin syntax
(begin expr ...)

Evaluates the exprs in order, returning the final result. The last expr is in tail position. If there are no exprs the result is void.

cond syntax
(cond (test body ...) ...)

Evaluates the test expressions left to right until one returns a truthy value, then evaluates the corresponding body expressions in tail position. If no test is truthy, the result is void.

define syntax
(define id value)

Binds a namespace-level variable id to the result of value.

At top-level, the value expression is evaluated before top-level bindings for the ids are created.

(define (id arg ...) body ...+)

Defines a procedure id, with formal arguments arg ... and the body. This form is equivalent to (define id (lambda (arg ...) body ...)).

element procedure
(element collection key)

Returns an element within a collection. The collection must be a non-null, non-empty list, sexp, or struct. The key must have a type appropriate for the collection: an int for lists or sexps, a string or symbol for structs.

(element [0, 1] 0)         =>  0
(element (sexp 0 1) 1)     =>  1
(element {f:2} "f")        =>  2
(element {f:3} (quote f))  =>  3

Since element is a procedure, field names must be quoted or else they will be evaluated as a variable reference:

(element {f:2} f)        => ERROR: Unbound variable reference
(let [(g "f")]
  (element {f:2} g))     => 2

An exception is raised if the collection has an unsupported type, if the key isn't appropriate for the collection, or if the key doesn't identify an element within the collection.

(element [0, 1] 2)       =>  ERROR
(element [0, 1] "2")     =>  ERROR
(element {f:2} "g")      =>  ERROR
elt procedure
(elt collection key)

Returns an element within a collection, being lenient. The collection must be a list, sexp, struct, or void. The key must have a type appropriate for the collection: an int for lists or sexps, a string or symbol for structs.

(elt [0, 1] 0)         =>  0
(elt (sexp 0 1) 1)     =>  1
(elt {f:2} "f")        =>  2
(elt {f:3} (quote f))  =>  3

If the collection is empty, null, or void, the result is void. If the key isn't appropriate for the collection, or if the key doesn't identify an element within the collection, the result is void.

(elt null.list 0)    =>  void
(elt [0, 1] 2)       =>  void
(elt [0, 1] "2")     =>  void
(elt {f:2} "g")      =>  void

Since elt is a procedure, field names must be quoted or else they will be evaluated as a variable reference:

(elt {f:2} f)        => ERROR: Unbound variable reference
(let [(g "f")]
  (elt {f:2} g))     => 2

An exception is raised if the collection has an unsupported type.

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
if syntax
(if test then else)

Evaluates the test expression first. If the result is truthy, evaluates the then expression and returns its value. Otherwise, evaluates the else expression and returns its value.

All values are "truthy" except for false, void, and any variant of null.

Note that only one of then or else expressions is evaluated, and both are in tail position.

For cases that need only one outcome, use when or unless.

is_blob procedure
(is_blob value)

Determines whether a value is of type blob, returning true or false.

is_bool procedure
(is_bool value)

Determines whether a value is of type bool, returning true or false.

is_clob procedure
(is_clob value)

Determines whether a value is of type clob, returning true or false.

is_decimal procedure
(is_decimal value)

Determines whether a value is of type decimal, returning true or false.

is_empty procedure
(is_empty collection)

Returns true if the size of the collection is zero, otherwise returns false.

is_float procedure
(is_float value)

Determines whether a value is of type float, returning true or false.

is_int procedure
(is_int value)

Determines whether a value is of type int, returning true or false.

is_list procedure
(is_list value)

Determines whether value is a list, returning true or false.

is_null procedure
 (is_null value)

Returns true when value is any Ion null, false otherwise.

is_null_null procedure
(is_null_null value)

Determines whether a value is null.null, returning true or false.

is_procedure procedure
(is_procedure value)

Returns true when value is a procedure, false otherwise.

is_sexp procedure
(is_sexp value)

Determines whether a value is a sexp, returning true or false.

is_string procedure
(is_string value)

Determines whether a value is of type string, returning true or false.

is_struct procedure
(is_struct value)

Determines whether value is a struct, returning true or false.

is_symbol procedure
(is_symbol value)

Determines whether a value is of type symbol, returning true or false.

is_timestamp procedure
(is_timestamp value)

Determines whether a value is of type timestamp, returning true or false.

is_void procedure
(is_void value)

Determines whether a value is the Fusion void value.

lambda syntax
(lambda (arg ...) body ...+)

Returns a procedure. When invoked, the caller's arguments are bound to the arg identifiers (the formal arguments) and the body is evaluated and returned. body may be one or more forms; the last form is in tail position and its result is the result of the procedure invocation.

The Fusion runtime system may optimize procedure instantiation, so it is unspecified whether one or more lambda expressions will return distinct or identical objects for any evaluation.

(lambda rest_id body ...+)

This variant, which declares a single formal argument rather than a sequence of them, returns a procedure that accepts any number of values, which are collected into an immutable sexp and bound to the rest_id:

((lambda args args) 8 9 10)   --> (8 9 10)
let syntax
(let ((ident expr) ...) body ...+)

Evaluates the exprs left to right, then binds each ident to its corresponding result, then evaluates body. The scope of the idents only covers the body, not the exprs.

body may be one or more forms; the last form is in tail position and its result is the result of the entire expression.

(let loop_id [(ident expr), ...] body ...+)

This variant also creates a procedure, bound to the given name loop_id, that accepts the same number of arguments as there are idents. When invoked, the procedure binds the idents to the arguments and evaluates the body.

For example, this snippet loops through the standard input port and writes the title field of each item:

(let loop [(item (read))]
  (unless (is_eof item)
    (let [(title (. item "title"))]
      (writeln title)
      (loop (read)))))

(While illustrative of looping, this is not the recommended way to accomplish this; see /fusion/io for better approaches.)

let_values syntax
(let_values (((ident ...) expr) ...) body ...+)

Captures multiple results, creating local bindings for the idents, with the body in scope. The exprs are evaluated left-to-right, and must return as many values as there are corresponding idents, which are then bound to those results. After the bindings are installed the body is evaluated. body may be one or more forms; the last form is in tail position and its result is the result of the entire expression.

letrec syntax
(letrec ((ident expr) ...) body ...+)

Creates new binding locations for each ident, binds them to their exprs, then evaluates body. The exprs are evaluated left-to-right, and the idents are bound in all exprs and bodys. body may be one or more forms; the result of the last form is the result of the entire expression.

lets syntax
(lets [(ident expr), ...] body ...+)

Like let, but each binding is created (and its expr evaluated) one by one, and the idents are bound in the following exprs as well as the body.

(lets [(a 1),
       (b (+ a 1))]
  [a, b])            => [1, 2]
module syntax
(module name language body ...+)

Declares a module containing the given body. The name must be a symbol; it is ignored when loading a module from a file.

The language must be an absolute module path. The denoted module is instantiated and all of its provided bindings are immediately imported. This "bootstraps" the module with a set of bindings that form the base semantics for the body. Unlike bindings that are required, these bindings can be shadowed by module-level definitions and by require statements.

When compiling a module, the body forms are partially macro-expanded in order to discover certain core forms like require and provide. The former are handled immediately, before other forms. The latter are handled after all other forms. At module level, the elements within begin forms are spliced into the enclosing module body, replacing the single begin form with its elements. This effectively enables module-level macro uses to expand into multiple forms.

not procedure
(not value)

Returns true if value is untruthy, false if value is truthy. Truthiness is as defined by if.

only_in syntax
(only_in module_path id ...)

A require clause that imports only the given ids from a module. If an id is not provided by the module, a syntax error is reported.

Bindings introduced by this form use the lexical context of the module path, not that of the given identifiers.

This form can only appear within require.

or syntax
(or expr ...)

Evaluates the exprs from left to right, returning the first truthy value that results (and ignoring further expressions). If they all return untruthy values, the last one is returned.

The last expr is in tail position. Given no exprs, the result is false.

prefix_in syntax
(prefix_in prefix_id module_path)

A require clause that adjusts each identifier to be bound by prefixing it with prefix_id.

Bindings introduced by this form use the lexical context of the module path, not that of the prefix identifier.

This form can only appear within require.

provide syntax
(provide provide_clause ...)

Declares bindings to be exported from the enclosing module. This form may only appear at module level.

Each provide_clause denotes some names to be exported. The following clause forms are allowed:

  • An identifier defined at module-level or imported from another module.
  • all_defined_out exports all module-level definitions.
  • rename_out exports selected bindings, giving them new names on the way out.

Within a module, a single provide form with multiple clauses behaves identically to multiple provide forms with single clauses.

quasiquote syntax
(quasiquote template)

Like quote, but the template datum may contain nested unquote forms that act as escapes. The unquoted expression is evaluated when the containing quasiquote is evaluated, and its result is inserted into the containing quoted datum.

(quasiquote [(+ 1 2), (unquote (+ 1 2))])

==> [(+ 1 2), 3]

unquote forms only escape one "level" of quasiquotation:

(let [(v 1)]
  (quasiquote (a (quasiquote (b (unquote v) (unquote (unquote v)))))))

==> (a (quasiquote (b (unquote v) (unquote 1))))
quote syntax
(quote datum)

Returns the Ion datum as-is, without evaluation.

rename_in syntax
(rename_in module_path (exported_id local_id) ...)

A require clause that imports each exported_id using the name local_id. If an exported_id is not provided by the module, a syntax error is reported.

In contrast to other require-clauses, bindings introduced by this form use the lexical context of the local_ids, not that of the module path.

This form can only appear within require.

Warning: This behaves differently than Racket's rename-in, which imports everything from the given module (or nested require-clause) while renaming selected items from that set. In contrast, this form ignores exported bindings that are not renamed.

rename_out syntax
(rename_out (local_id exported_id) ...)

A provide clause that exports each local_id using the name exported_id. This effectively renames the binding on export.

This form can only appear within provide.

require syntax
(require require_clause ...+)

Declares bindings to be imported into the enclosing namespace. This form may only appear at module level or top level.

Each require_clause denotes some bindings to be imported. The following clause forms are allowed:

  • A string or symbol containing a module path; all names provided by the referenced module are imported. The bindings introduced by this form use the lexical context of the module path.
  • only_in enumerates a set of names to import.
  • prefix_in provides a prefix to imported bindings.
  • rename_in renames specified bindings.

Within a module, require declarations are processed before other forms, regardless of their order within the module source, and imported bindings are scoped across the entire module. No identifier may be imported multiple times, unless all such bindings refer to the same originating definition. Furthermore, no identifier may have both an import and a module-level definition. In other words: module-level bindings introduced by require or define must not conflict, although either may shadow same-named bindings introduced by the module's language declaration.

At top level, require will replace an existing import, and may shadow an existing top-level definition.

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
size procedure
(size collection)

Returns the number of elements in the collection. The size of null.list (etc.) is zero. If collection is an improper sexp, an exception is thrown.

Warning: Computing the size of an sexp takes linear time, since it must traverse the linked list of pairs to count elements.

type_annotations procedure
(type_annotations value)

DEPRECATED as of Fusion R20; renamed to annotations.

unless syntax
(unless test body ...)

Evaluates the test, and if it is not truthy, evaluates the body forms left to right. The last body is in tail position, and its result is the result of the entire form. If the body isn't evaluated, the result is void.

A companion form is when.

unquote syntax
(unquote expr)

Used as an escape within a quasiquote form; not valid in any other context.

values procedure
(values value ...)

Produces multiple results, returning the zero or more values. Usually used in conjuction with let_values to bind the results to names.

void procedure
(void arg ...)

Returns the singular void value, ignoring all args.

when syntax
(when test body ...)

Evaluates the test, and if it is truthy, evaluates the body forms left to right. The last body is in tail position, and its result is the result of the entire form. If the body isn't evaluated, the result is void.

A companion form is unless.

#%top syntax
('#%top' id)

References a top-level definition for symbol id, skipping over any surrounding local bindings. Within a module, id must be defined within the module and not locally.

As suggested by the awkward name, this form is rarely needed by application code and is primarily an artifact of the macro-expansion process.