Module /fusion/struct
Operations for structs.
A struct is an unordered collection of values, keyed by strings. Since Fusion structs are based on Ion structs, these collections are multi-maps: the same key can map to multiple values, or even multiple mappings to the same value. The elements of a struct are its values, but not the associated field names.
Because structs are unordered, operations that iterate over field keys or values do not guarantee a repeatable order of iteration: the order may change at any time, even between successive procedure calls. This (lack of) contract most commonly affects unit tests, which must be careful to compare actual and expected values using order-insensitive comparisons.
Structs come in two concrete types: immutable and mutable. The predicates
is_immutable_struct and is_mutable_struct distinguish between them.
Creating Structs
In standard Fusion, Ion struct syntax denotes immutable values, treating field
names as literals and field values as expressions. Thus
{} denotes an immutable struct of size zero, and {f:[x]} denotes an
immutable struct of size 1 holding a list whose only element is the value of
the variable x. The value of a struct
literal is immutable even when some child values are evaluated at run-time,
but elements of such a struct may be mutable.
Quoted forms are also immutable; in (quote {f:x}) the struct's sole element
is the symbol 'x'.
The procedure struct works like a struct literal, creating an immutable
struct from names and elements:
(struct "name" "Steve" "age" 29) => {name:"Steve", age:29}
The procedure mutable_struct similarly creates mutable structs:
(mutable_struct "name" "Taylor" "age" 17) => {name:"Taylor", age:17}
Note that the default "ionization strategy" renders all struct types the same, so the results look similar even though the values have different types. Eventually the application will be able to control this strategy; these defaults are designed to allow a Fusion developer to construct data in various combinations of mutability (etc.) and output it as "normal" Ion data.
struct_zip and mutable_struct_zip provide another way to create (immutable
and mutable) structs. They accept two lists containing the names and elements:
(struct_zip ["name", "age"] ["Doug", 38]) => {age:38, name:"Doug"}
Modifying Structs
Fusion provides data modification operators that have a functional style, even when the modification involves mutation. The functional and mutating variants have the same signature and operate over both immutable and mutable types; in general they are interchangable when used in a functional style.
Changing the key-value mappings of a struct is performed via put and
put_m, which guarantee that the result has only a single mapping for
the key:
(put {a:1, b:9, b:12} "b" 11) => {a:1, b:11}
The procedures remove_keys and retain_keys functionally eliminate fields
from a struct without modifying the input; remove_keys_m and retain_keys_m
modify the input when possible. struct_merge and struct_merge_m combine
the fields from two structs.
Iterating Struct Fields
struct_iterator creates a multi-valued iterator over
a struct's key-value pairs.
The procedure struct_do iterates the name/value pairs within a struct,
but since the input isn't modified one must use side effects.
More general collection operations like any and
find can be applied to structs, in which case the field names are ignored
while the field values are iterated.
Exported Bindings
.anydoelementelteveryfindhas_keyis_collectionis_emptyis_immutable_structis_mutable_structis_structmutable_structmutable_struct_zipnoneputput_mremove_keysremove_keys_mretain_keysretain_keys_msizestructstruct_dostruct_iteratorstruct_mergestruct_merge_mstruct_unzipstruct_zip
(. value key ...)
Traverses a "path" through a data structure, folding the value through each
key in turn. When the value is void, it is returned immediately and any
further keys are not applied. If a key is a procedure, it must accept one
argument; the procedure is applied to the current value, and the result becomes
the value for the next key. Otherwise, the key and value are passed to elt
to get the next value.
(. [0, 1] 0) => 0
(. (sexp 0 1) 1) => 1
(. {f:2} "f") => 2
(. {f:3} (quote f)) => 3
(. [0, 1, 2, 3] size) => 4
(. (sexp 0 1 2 3) head) => 0
(. (sexp 0 1 2 3) tail) => (1 2 3)
(. (sexp 0 1 2 3) tail tail head) => 2
Since . is a procedure, field names must be quoted or else they will
be evaluated as a variable reference:
(. {f:2} f) => ERROR: Unbound variable reference
(let [(g "f")]
(. {f:2} g)) => 2
(any pred collection)
Applies the one-argument predicate pred to the elements of collection;
the first time pred returns a truthy value that truthy value is returned and
no more elements are visited. If no call returns a truthy value, then the
result is that of the final predicate call, or false if the collection is
empty.
When collection is a sequence, the elements are visited in order, and the
application of pred to the final element of the sequence is in tail position.
(do proc collection)
Applies the one-argument procedure proc to the elements of collection,
ignoring any results. Returns void.
When collection is a sequence, the elements are visited in order.
See also: struct_do
(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 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.
(every pred collection)
Applies the one-argument predicate pred to the elements of collection;
the first time pred returns an untruthy value that untruthy value is returned
and no more elements are visited. If no call returns an untruthy value, then the
result is that of the final predicate call, or true if the collection is empty.
When collection is a sequence, the elements are visited in order, and the
application of pred to the final element of the sequence is in tail position.
(find pred collection)
Applies the one-argument predicate pred to each element of collection;
the first time pred returns a truthy value that element is returned.
If no such element is found, the result is void.
When collection is a sequence, the elements are visited in order.
When collection is a struct, the "elements" of the collection are its
values (as opposed to its key-value pairs): the predicate will be applied on
each value, and the result is either one of those values or void.
(has_key collection key)
Determines whether a collection has a mapping for a given key.
When has_key returns true, then (element collection key) will succeed.
Note that the keys of a sequence are the zero-based integer indices of the elements within the sequence, not the elements themselves.
(has_key {f:12} "f") ==> true
(has_key [3,true,2014T] 0) ==> true
(has_key [3,true,2014T] 3) ==> false
(has_key [3,true,2014T] null) ==> false
(is_collection value)
Determines whether value is a collection (struct, list, or sexp), returning
true or false.
(is_empty collection)
Returns true if the size of the collection is zero, otherwise returns
false.
(is_immutable_struct value)
Determines whether value is an immutable struct, returning true or false.
(is_mutable_struct value)
Determines whether value is a mutable struct, returning true or false.
(is_struct value)
Determines whether value is a struct, returning true or false.
(mutable_struct name value ... ...)
Constructs a mutable, non-null struct from alternating strings and values.
Each name is a non-empty string or symbol denoting a field name, and the
following value is the field's value. Names may be repeated to produce
repeated (multi-mapped) fields.
(mutable_struct_zip names values)
Constructs a mutable struct from a list of field names and a list of values. If the lists have unequal lengths, only the first n elements will be used where n is the shorter length.
The names must be non-empty strings or symbols.
(struct_zip ["f", "g"] [1, 2]) => {f:1,g:2}
(struct_zip ["f", "f"] [1, 2]) => {f:1,f:2}
(struct_zip ["f"] [1, 2]) => {f:1}
(none pred collection)
Applies the one-argument predicate 'pred' to the elements of collection. Returns false if the predicate returns a truthy value for any element, true if none do, and true if the collection is empty.
(put struct key value)
Functionally modifies a field of a struct, returning a new struct of the same
type. Any existing fields (including repeats) named by the key are replaced
by a single field with the value.
(define s {a:1, b:2, b:3})
(put s "a" 4) ==> {b:2,b:3,a:4}
(put s "b" 5) ==> {b:5,a:1}
(put s "c" 6) ==> {b:2,b:3,c:6,a:1}
s ==> {b:2,b:3,a:1}
(put_m struct key value)
Modifies a field of a struct, mutating the struct when possible and
returning a struct of the same type. Any existing fields (including repeats)
named by the key are replaced by a single field with the value.
When given an immutable struct, put_m behaves identically to put:
(define s {a:1, b:2, b:3})
(put_m s "a" 4) ==> {b:2,b:3,a:4}
(put_m s "b" 5) ==> {b:5,a:1}
(put_m s "c" 6) ==> {b:2,b:3,c:6,a:1}
s ==> {b:2,b:3,a:1}
When given a mutable struct, the struct is mutated:
(define s (mutable_struct "a" 1 "b" 2 "b" 3))
s ==> {b:2,b:3,a:1}
(put_m s "a" 4) ==> {b:2,b:3,a:4}
(put_m s "b" 5) ==> {b:5,a:4}
(put_m s "c" 6) ==> {b:5,c:6,a:4}
s ==> {b:5,c:6,a:4}
(remove_keys struct name ...)
Returns a struct derived from struct without fields with the given names.
If the input is null.struct then the result is null.struct. The result
will have the same annotations as struct.
(remove_keys_m struct name ...)
Returns a struct similar to struct without fields with the given names.
The result may share structure with the input, which may be mutated.
If the input is null.struct then the result is null.struct. The result
will have the same annotations as struct.
(retain_keys struct name ...)
Returns a struct derived from struct with only fields with the given
names. If the input is null.struct then the result is null.struct. The
result will have the same annotations as struct.
(retain_keys_m struct name ...)
Returns a struct similar to struct with only fields with the given
names. The result may share structure with the input, which may be mutated.
If the input is null.struct then the result is null.struct. The result
will have the same annotations as struct.
(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.
(struct name value ... ...)
Constructs an immutable, non-null struct from alternating strings and values.
Each name is a non-empty string or symbol denoting a field name, and the
following value is the field's value. Names may be repeated to produce
repeated (multi-mapped) fields.
(struct_do proc struct)
Iterates the fields of struct for side-effects, applying proc to each
name/value field. Returns struct. The proc must accept two arguments, a
field name symbol and a value; any results from applying the procedure are
ignored.
See also: do
(struct_iterator struct)
Returns an iterator over the content of struct. Calls to iterator_next
will return two results representing a single field: the field's name (as a
symbol) and the field's value.
(define (show struct)
(let [(iter (struct_iterator struct))]
(let loop []
(when (iterator_has_next iter)
(let_values [((k v) (iterator_next iter))]
(displayln k " --> " v)
(loop))))))
(show {foo:"bar", hello:"goodbye", yes:false})
This code displays:
yes --> false
hello --> goodbye
foo --> bar
(struct_merge struct1 struct2)
Returns a struct that has all the name-value elements of both arguments. This will result in repeated fields if the names overlap or if one of the arguments has repeats. The result has the same type as the first argument.
(struct_merge {a:1} {a:1}) ==> {a:1, a:1}
(struct_merge {a:1} {b:2}) ==> {a:1, b:2}
(struct_merge_m struct1 struct2)
Returns a struct that has all the name-value elements of both arguments, mutating the first argument when possible. This will result in repeated fields if the names overlap or if one of the arguments has repeats. The result has the same type as the first argument.
(struct_unzip struct)
Deconstructs a struct and returns two values: a list of field names (as
symbols) and a list of associated values. This is the inverse of
struct_zip.
(let_values [((keys vals) (struct_unzip {a:1, b:2}))]
(struct_zip keys (map - vals)))
=>
{b:-2,a:-1}
This procedure returns multiple results, not
a pair or sequence, so it must be called from a context that expects that, like
a let_values clause with two bound
identifiers.
(struct_zip names values)
Constructs an immutable struct from a list of field names and a list of values. If the lists have unequal lengths, only the first n elements will be used where n is the shorter length.
The names must be non-empty strings or symbols.
(struct_zip ["f", "g"] [1, 2]) => {f:1,g:2}
(struct_zip ["f", "f"] [1, 2]) => {f:1,f:2}
(struct_zip ["f"] [1, 2]) => {f:1}