fble-0.5 (2025-07-13,fble-0.4-212-ga8f8ad0f)
This tutorial does a deep dive into structs. By the end of this tutorial, you'll be fully versed in section 3 of the fble language spec on structs.
A struct is a grouping of named values. You create a struct value by providing the value of each field of the struct. You can later access those values by name.
Here are examples of how we've used structs so far:
@ Bit4@ = *(Bit@ 3, Bit@ 2, Bit@ 1, Bit@ 0); Bit4@ X = Bit4@(0, 0, 1, 1); (Bit4@, Bit4@) { Bit4@; } And4 = (Bit4@ a, Bit4@ b) { Bit4@(And(a.3, b.3), And(a.2, b.2), And(a.1, b.1), And(a.0, b.0)); };
The syntax *(...)
is used for a struct type, with a list of named and
typed fields. To create a struct value, you specify the struct type followed
by the list of values for each field in order. You can access fields of a
struct using .
, as in a.3
.
That describes the fundamentals of what you can do with structures.
Some miscellaneous details:
Structs can have zero or more fields.
Field names follow the same naming conventions as variable names (see the
Variables
tutorial for details).
Struct types are equal if they have the same order, types, and names of all of their fields.
So far we've always constructed struct values by providing the struct type
explicitly. For example: Bit4@(0, 0, 1, 1)
. It's also possible to
specify a struct value where the type of the struct is implicit. In this
case we specify the names of the fields explicitly, and rely on the compiler
to infer the types of the fields.
For example, the follow two lines are equivalent:
Bit4@ X = Bit4@(0, 0, 1, 1); Bit4@ X = @(3: 0, 2: 0, 1: 1, 0: 1);
Because the compiler knows the type of 0
is Bit@
, and the type of
1
is Bit@
, it can infer from @(3: 0, 2: 0, 1: 1, 0: 1)
the
struct type: *(Bit@ 3, Bit@ 2, Bit@ 1, Bit@ 0)
.
Sometimes you want to modify the fields of a struct value. Values in fble are immutable, so you can't modify struct fields directly. What you can do is make a new copy of a struct value where some of the fields are modified.
You could do this with the syntax we've already presented for constructing
struct values. For example, to change bit 2 of our Bit4@
struct X
above:
Bit4@ Y = Bit4@(X.3, 1, X.1, X.0);
For large structures, it becomes tedious to manually copy each of the
fields. Fble provides a short hand syntax that allows you to copy a struct
value without listing all of its fields. For the struct copy syntax, you
provide a struct value to copy, such as X
, followed by syntax .@
,
followed by the list of new field values using the syntax from implicit
type struct values.
The above example can be rewritten using struct copy syntax as:
Bit4@ Y = X.@(2: 1);
If you want, you can modify multiple fields at once and have more complicated field value expressions. If modifying multiple fields, the fields must be specified in the same order as they are defined in the struct type. For example:
Bit4@ Z = X.@(2: 1, 0: And(X.3, X.0));
Aside from their normal use in programming, structs play an important role in namespace management.
For example, the variable names 0
and 1
are nice to have for the
Bit@
type, but they are also useful names for Bit4@
and Int@
types. We can create a struct value to hold 0
and 1
for the Bit@
type rather than pollute the global namespace of variables.
For example, let's define a struct value called Bits
that has two
fields, 0
and 1
, which have the Bit@
values for 0
and 1
respectively:
@ Bits@ = *(Bit@ 0, Bit@ 1); Bits@ Bits = Bits@(Bit@(0: Unit), Bit@(1: Unit));
Now we can refer to bits 0
and 1
via the Bits
struct value. For
example:
Bit4@ X = Bit4@(Bits.0, Bits.0, Bits.1, Bits.1);
When defining modules, it's common to define variables in scope, and export them by bundling them together in a struct value. For example:
Bits@ Bits = { Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); Bits@(0, 1); };
In these cases, we can use a special short hand supported for implicit struct type values: if the argument of a field is a variable, you can leave out the field name; the variable name will be used for the name of the field.
For example, here's how we could define the Bits
value using an implicit
type struct value with explicit fields:
Bits@ Bits = { Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); @(0: 0, 1: 1); };
And here's how we could define the Bits
value using an implicit type
struct value with implicit fields:
Bits@ Bits = { Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); @(0, 1); };
You can mix implicit fields with explicit fields in implicit type struct
values, which is occasionally useful for renaming things when you create the
struct value. For example, maybe we want to add zero
and one
as
alternate ways to refer to those bits:
% Bits = { Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); @(0, 1, zero: 0, one: 1); }
Notice we switch to using the kind %
in the definitions of Bits
,
which means we don't have to define the Bits@
type explicitly anywhere.
Functions are values, which means functions are allowed in fields of
structures. There's nothing special about this. For example, we could add
our And
function to the Bits
struct value we've been working with:
% Bits = { Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); (Bit@, Bit@) { Bit@; } And = (Bit@ a, Bit@ b) { a.?(0: 0, 1: b); }; @(0, 1, And); }
Here's how we might call that function:
(Bit4@, Bit4@) { Bit4@; } And4 = (Bit4@ a, Bit4@ b) { Bit4@(Bits.And(a.3, b.3), Bits.And(a.2, b.2), Bits.And(a.1, b.1), Bits.And(a.0, b.0)); };
Though we haven't discussed polymorphism yet, polymorphic values and functions are also allowed for struct fields.
Types are values, which means types are allowed in fields of structures. The
same naming rule applies as for variable names: type fields must be followed
by @
.
For example, we could add the Bit@
type to our Bits
bundle:
% Bits = { @ Bit@ = +(Unit@ 0, Unit@ 1); Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); @(Bit@, 0, 1); };
And here is how we could use that type field:
@ Bit4@ = *(Bits.Bit@ 3, Bits.Bit@ 2, Bits.Bit@ 1, Bits.Bit@ 0); Bit4@ X = Bit4@(Bits.0, Bits.0, Bits.1, Bits.1);
Note that the type of the fields for Bit4@
is Bits.Bit@
. This is a
little strange because it's a type that depends on a value. That doesn't
mean the compiler has to evaluate code at compile time to do proper type
checking though. The compiler can figure out the value of the Bits.Bit@
field from the type of the Bits
struct, because it knows the type of the
Bit@
field is @<Bit@>
, which means its value must be Bit@
regardless of how the Bits
value was constructed.
This is how we use struct values to define types, functions, and values in modules for reuse in other modules.
For the curious, here's how we could define that same Bits
struct value
using explicit types:
% Bits = { @ Bit@ = +(Unit@ 0, Unit@ 1); Bit@ 0 = Bit@(0: Unit); Bit@ 1 = Bit@(1: Unit); @ Bits@ = *(@<Bit@> Bit@, Bit@ 0, Bit@ 1); Bits@(Bit@, 0, 1); };
Notice the use of the typeof operator to get the type of the Bit@
type,
and the name Bit@
for the field.
You now know everything there is to know about structs in fble. To reinforce
this, read over section 3 of the fble
language specification
. Everything there should be familiar to you now.
Go back to the Basics tutorial code and group together the definitions of
Bit@
, 0
, 1
, And
, and ShowBit
into a single struct value
called Bits
. Experiment with both explicit and implicit type struct
values, and implicit and explicit field names.
In the Basics tutorial, group together code related to the Bit4@
type
together into a single Bit4
struct value. Rename And4
to And
and
rename ShowBit
and ShowBit4
both to Show
now that we have the
Bits
and Bit4
namespaces to distinguish between them.
Head over to the Modules
tutorial to learn all about
modules.