View all results

Type ordering

FQL defines a deterministic relational order for non-Duration built-in values. It is used by <, <=, >, and >=, sorting, and recursive array and object comparison.

When two non-Duration built-ins have different types, their type decides the result:

example.fql
read-only
none < bool < number < string < datetime < binary < array < object

Values are compared within their type only after the types match. Int and Float share the numeric comparison domain and compare by numeric value.

example.fql
read-only
none < false false < true true < 0 1 < 2.5 0 < "0" "abc" < [] [] < {}

Duration values

Duration is intentionally separate from the cross-type chain. Two Durations compare by signed nanosecond value, and equivalent units compare equal.

example.fql Ferret v2
query.fql
FQL
return { less: 500ms < 1s, equal: 1000ms == 1s }

A native Duration and any non-Duration value are unequal. Relational comparison between them is invalid in either operand order.

example.fql
read-only
1s == 1000 // false 1s != "1s" // true 1s < 1000 // runtime error "1s" >= 1s // runtime error

Use to_duration explicitly when the other value should be interpreted as a Duration.

Sorting uses the same relational contract, so a collection that mixes Duration with another type cannot be sorted without first normalizing its values.

example.fql
read-only
return arrays::sorted([1s, "2s"]) // runtime error

Equality, membership, and uniqueness

Equality does not use the cross-type order to make different types equal. Membership, match, grouping, set operations, and deduplication all verify canonical equality recursively.

Equivalent native Durations collapse to the first representative, while strings and numbers remain distinct from Duration:

example.fql
read-only
distinct ["1s", 1000, 1s] // keeps all three values distinct [1s, 1000ms] // keeps the first Duration

Hashes may select candidate buckets internally, but equality determines whether values are actually the same.

Primitive values

Within a built-in type:

  • none is equal only to none.
  • Booleans use false < true.
  • Numbers compare by numeric value across Int and Float.
  • Durations compare by signed nanoseconds, only with Duration.
  • Strings compare lexically and case-sensitively.
  • DateTime values compare by canonical instant.
  • Binary values compare by their byte contents.

DateTime comparison does not parse strings or convert numeric epoch values. Use to_datetime before comparison when conversion is intended.

Arrays

Arrays compare element by element from left to right. The first unequal element decides the result; if every shared element is equal, the shorter array comes first.

example.fql
read-only
[] < [0] [1] < [2] [1, 2] < [1, 3] [1] < [1, 0]

Array comparison is recursive. A nested Duration/non-Duration pair remains unequal for equality and invalid for relational comparison.

example.fql
read-only
[1s] == ["1s"] // false [1s] < ["1s"] // runtime error

Objects

Objects compare their attributes rather than declaration order. Attribute names are considered in sorted order, and corresponding values use the same recursive comparison rules.

If one object lacks an attribute present in the other, the missing value is treated as none for comparison.

example.fql
read-only
{} < { "a": 1 } {} == { "a": none } { "a": 1 } < { "a": 2 } { "a": true } < { "a": 0 }

Declaration order does not affect equality:

example.fql Ferret v2
query.fql
FQL
return { "a": 1, "b": 2 } == { "b": 2, "a": 1 }

Host values

Host values participate only through compatible comparison capabilities supplied by the embedding application. Equality and relational comparison are separate capabilities; an opaque host value does not become comparable through its String representation or a generic numeric conversion.

See Capability Types and Host Values for the host contracts.

Next steps