Literals

fble-0.5 (2025-07-13,fble-0.4-212-ga8f8ad0f)

This tutorial describes special syntax fble has for user defined literals.

Working Without Literals

Many languages have special syntax for integer and string literals. The fble language doesn't have built in integer, character, or string types. Without special support for literals, it can be very tedious to specify integer or string values.

For example, imagine we want to specify an integer value 2023 representing the current year. Without literals we have a few options for how to do this. Assuming integer values 0 through 10 are defined, we could write it as:

Int@ 20 = Mul(10, 2);
Int@ 202 = Add(Mul(10, 20), 2);
Int@ 2023 = Add(Mul(10, 202), 3);

A better way would be to write a helper function that can convert a list of digits to an integer. For example:

(List@) { Int@; } MakeInt = {
   (Int@, List@) { Int@; } Helper = (Int@ int, List@ digits) {
      digits.?(nil: int);
      Helper(Add(Mul(10, int), digits.cons.head), digits.cons.tail);
   };

   (List@ digits) {
     Helper(0, digits);
   };
};

Because our MakeInt function takes a list of integers as its single argument, we can use a list expression to define our integer:

Int@ 2023 = MakeInt[2, 0, 2, 3];

This approach may be convenient enough for small integers. But imagine using the same approach for strings:

String@ hello_world = MakeString[h, e, l, l, o, space, w, o, r, l, d];

That quickly becomes tedious.

Literal Expressions

The fble language has syntax for a literal expression, which is a more compact way of passing a list of letters to a function. Here are the same examples above, rewritten using literal expressions:

Int@ 2023 = Int|2023;
String@ hello_world = String|'hello, world';

A literal expression is a function followed by | and then a single word. In this case, Int is a function that converts a list of letters to an integer and 2023 is a word with the list of letters to pass to the Int function. Similarly, String is a function that converts a list of letters to a string and 'hello, world' is a word with the list of letters to pass to the String function.

The only tricky part is specifying what we mean by a letter. For literals in fble, the type of a letter is a union type that has fields with single-letter names whose types are the struct type with no fields.

For example, for the letters used in integer literals, we define a type Decimal@:

@ Decimal@ = +(
  Unit@ 0, Unit@ 1, Unit@ 2, Unit@ 3,
  Unit@ 4, Unit@ 5, Unit@ 6, Unit@ 7,
  Unit@ 8, Unit@ 9
);

When the compiler sees a literal expression with the word 2023, it splits it up into letters 2, 0, 2, 3, and uses those to construct Decimal@ values directly. The literal expression Int|2023 gets converted into a list expression:

Int@ 2023 = Int[Decimal@(2: Unit), Decimal@(0: Unit), Decimal@(2: Unit), Decimal@(3: Unit)];

Except, in reality, the compiler would use the value *()() instead of Unit, because it doesn't know about the name Unit.

In summary, a literal expression can be used in place of list expressions in the special case where the type of element of the list is a union type with single-letter named fields whose types are the struct type with no fields.

Defining Literals for Bit4@

Let's define our own literals for the Bit4@ type. We already have a letter type we can use the satisfies the requirements for literal expressions:

@ Bit@ = +(Unit@ 0, Unit@ 1);

That means all we need to do is define a function that takes a list of Bit@ and produces a Bit4@. Because we haven't introduced polymorphism yet, let's redefine our own list type:

@ BitList@ = +(*(Bit@ head, BitList@ tail) cons, Unit@ nil);

Next we need to decide how we will handle literals with too few or too many bits. Let's say literals like 1 and 10 are treated like 0001 and 0010, and literals like 10110 are truncated to be like 0110. A simple way to implement this would be start with a Bit4@ value 0 and shift in bits from the literal.

(Bit@, Bit4@) ShiftIn = (Bit@ bit, Bit4@ x) {
  Bit4@(x.2, x.1, x.0, bit);
};

(BitList@) { Bit4@ } Bit4 = {
  (Bit4@, BitList@) { Bit4@; } Helper = (Bit4@ x, BitList@ bits) {
    bits.?(nil: x);
    Helper(ShiftIn(x, bits.cons.head), bits.cons.tail);
  };

  (BitList@ bits) {
    Helper(Zero, bits);
  };
};

That's it. We now can write literals for the Bit4@ type. Here are some examples:

Bit4@ a = Bit4|0;
Bit4@ b = Bit4|1010;
Bit4@ c = Bit4|110010;

Note that this only works because the Bit@ type was defined with field names 0 and 1. If instead we had defined Bit@ with field names zero and one, you could not use it for the type of letters in a literal expression. In that case, you can define a separate letter type with field names 0 and 1 for use in your literals.

Different Kinds of Literals for the Same Type

For the Int@ type, we can define integer literals that take decimal numbers. In this case, the letter type is Decimal@, decimal digits. If we wanted to, we could as easily provide integer literals that take binary, octal, or hexidecimal digits. The literal function is what's used to distinguish between them. For example, we could have all of the following:

Int@ 42 = BinaryInt|101010;
Int@ 42 = OctalInt|52;
Int@ 42 = Int|42;
Int@ 42 = HexInt|2a;

Invalid Literals

In a literal expression such as Int|2023, the compiler knows what the element type is based on the function Int. From this it can infer what the legal letters are to appear in the literal word. For example, if you wrote Int|20y3, it would give a type error, because there is no field named y in the Decimal@ type.

The compiler can't do any more than that to validate the syntax of the literal though. For instance, maybe you want to write a negative integer literal as: Int|'-123'. For that to work, you would need to add '-' as a field to Decimal@, and then the compiler will accept words like Int|'12-41', even though those aren't what we think of as legal integer literals.

There are a few ways to handle syntax errors in literals that consist only of the letters you expect:

Literal Expressions versus Quoted Words

The fble language allows you to define variable and field names with punctuation in them using single quotes. For example:

Float@ 'one plus two' = 3;        # A variable with spaces in the name.

Single quotes are used in general in fble for any single words that contain punctuation. This is often useful in literals, because literals often contain punctuation:

String@ example = String|'one plus two';

You don't have to use single quotes if your literal doesn't contain punctuation:

String@ example = String|one;

Re-using Literal Functions

Literal functions like Int, Bit4, and String that we have shown here can be used as is for list expressions if you want. This can be convenient when defining values where one or more digits are derived programmatically instead of being constant. No need to define a separate list function, you can use your literal function as is!

You can also reuse literal functions as normal functions in fble, passing a list value directly to them.

For example:

Bit4@ a = Bit4|1010;
Bit4@ b = Bit4[1, 0, one, zero];

BitList@ bits = ...;
Bit4@ c = Bit4(bits);

Empty Literals

Empty literals are allowed. For example, you might write the empty string as:

String@ empty = String|'';

You could also write this using list expressions or as a normal function call:

String@ empty = String[];
String@ empty = String(EmptyList);

Longer Letters

The main use of literals, and all of the examples we've shown so far, have single-character letters. Technically speaking, a single letter could be made up of multiple characters. In that case, the next letter of the literal word is the longest letter that is a prefix of the word.

For example, say our letter type was defined as:

@ Letter@ = +(Unit@ a, Unit@ ab, Unit@ b);

(Letter@) { Letter@; } Letter = (Letter@ x) {
  x;
};

The literal Letter|aabb is broken down into the three element sequence a, ab, b.

This behavior exists to better support multi-byte characters, where it's harder to say where one character ends and the next character begins. Otherwise there's probably not much practical use for longer letters in literals.

Literals in the Language Specification

Literals are described in section 7.2 of the fble language specification.

Exercises

Define a letter type for lowercase letters a through z and the space character and a string type that is a list of those letters. Write your own literal function and define the string value hello world using a literal expression.

Next Steps

Head over to the Polymorphism tutorial to learn all about polymorphism.