fble-0.5 (2025-07-13,fble-0.4-212-ga8f8ad0f)
This tutorial shows how common programming language features map to the fble language. You can use this to get a quick overview and first peek of the fble language in preparation for diving into the details in subsequent tutorials.
Comments in fble start with the #
character end continue to the end of
the line. For example:
# Here is a comment line.
Fble does not have builtin primitive data types such as bool, char, int, or
float. These data types can be implemented without special language support;
they are available in the core library modules /Core/Bool%
,
/Core/Char%
, and /Core/Int%
. Library support for floating point
numbers has not yet been implemented.
Fble has special syntax for user-defined literals. The core library module
/Core/Int/Lit%
provides a decimal integer literal. The library could
also provide hexidecimal or other integer literals if needed:
Int@ year = Int|2023; Int@ hash = Hex|D76AA478;
Variables in fble can be declared globally or at any block level. Variables scope over the block in which they are defined. For example:
Int@ three = Int|3; Int@ seven = { Int@ four = Int|4; Add(three, four); };
Fble supports user defined structure types. For example:
@ Person@ = *(String@ name, Int@ age); Person@ george = Person@(Str|'George', 8); Person@ fred = @(name: Str|'fred', age: 10); Int@ average_age = Div(Add(george.age, fred.age), 2);
Fble has a discriminated union type. An implicit tag is stored with the union value to track which field of the union is valid. For example:
@ Expr@ = +( *(Expr@ a, Expr@ b) add, *(Expr@ a, Expr@ b) sub, *(Expr@ a) neg, Int@ constant ); Expr@ one = Expr@(constant: Int|1); Int@ value = one.constant; Expr@ two = Expr@(add: @(a: one, b: one)); Expr@ first_operand = two.add.a; Bool@ is_binary_op = one.?(add: True, sub: True, neg: False, constant: False);
Fble supports enums as a special case of union types. For example:
@ Move@ = +(Unit@ rock, Unit@ paper, Unit@ scissors); Move@ Rock = Move@(rock: Unit); Move@ Paper = Move@(paper: Unit); Move@ Scissors = Move@(scissors: Unit); (Move@) { Bool@; } MoveBeatsRock = (Move@ move) { move.?(rock: False, paper: True, scissors: False); };
Fble does not have an array type. You can use a map from the /Core/Map%
library instead to map integer indices to values.
The core library module /Core/List%
defines a list type and functions
for working on lists.
Fble has special syntax for constructing list-like values.
List@ numbers = List[1, 4, f(3), 7];
Fble does not have a builtin string type. The core library module
/Core/String%
provides a string type you can use if you like.
Fble has special syntax for user-defined literals. The core library module
/Core/String%
provides some string literal variants:
String@ text = Str|'hello, world'; String@ multiline = StrE|'hello, world\nhow are you?\n';
Fble does not have a pointer type. Automatic memory management takes care of
the allocation and freeing of objects on the heap automatically. You can use
a map from the /Core/Map%
library to simulate pointers in the language.
Fble uses a garbage collector for automatic memory management. Garbage collection is incremental and only does work when you allocate new objects.
The fble implementation tries to avoid heap allocations under the covers where possible to improve performance.
Fble has a conditional operator to provide control flow. It can be used in statements, replacing if, else, and switch statements common in other languages.
For example:
# .? Used as a conditional operator: Int@ min = Lt(x, y).?(true: x, false: y); # .? Used like an if/else statement: Grade@ grade = { Gt(testscore, 90).?(true: A); Gt(testscore, 80).?(true: B); Gt(testscore, 70).?(true: C); Gt(testscore, 60).?(true: D); F; }; # .? Used like a switch statement: Int@ gpa = grade.?(a: 4, b: 3, c: 2, d: 1, f: 0);
The conditional operator works on simple union types. It cannot be used to switch on complex string or integer data types. For those purposes, you can define helper functions that return simple union types and perform control flow based on the results.
Fble has no built-in loop construct. You can implement loops using recursive functions. You can define your own loop functions and call those.
For example:
# Looping directly with recursive function calls. (Int@) { Int@; } Factorial = (Int@ n) { IsZero(n).?(true: 1); Mul(n, Factorial(Sub(n, 1))); }; # Looping using a library function. Int@ total = /Core/List%.ForEach(list, 0, (Int@ elem, Int@ sum) { Add(elem, sum); });
Here is an example function that tests if y is a multiple of x:
(Int@, Int@) { Bool@; } Divides = (Int@ x, Int@ y) { IsZero(Mod(y, x)); };
Fble does not support default or keyword arguments to functions.
Fble does not support variable argument functions directly. Variable argument functions can be simulated in fble by passing a list as the final argument to the function, or using a list literal function. All of the variable arguments will need to have the same type.
For example:
(List@<Int@>) { Int@; } Sum = (List@<Int@> args) { args.?(nil: 0); Add(args.cons.head, Sum(args.cons.tail)); }; Int@ sum_to_4 = Sum[1, 2, 3, 4]; Int@ sum_to_5 = Sum[1, 2, 3, 4, 5];
Fble supports arbitrary nesting of function, type, and other definitions.
Here's an example from the core library code to convert a string to an
integer. It defines inner helper functions Digit
and ReadP
.
(String@) { Int@; } Read = { (Char@) { Int@; } Digit = (Char@ c) { c.?( '0': Int|0, '1': Int|1, '2': Int|2, '3': Int|3, '4': Int|4, '5': Int|5, '6': Int|6, '7': Int|7, '8': Int|8, '9': Int|9, : Int|0); }; (Int@, String@) { Int@; } ReadP = (Int@ x, String@ s) { s.?(nil: x); ReadP(Add(Mul(Int|10, x), Digit(s.cons.head)), s.cons.tail); }; (String@ s) { s.?(nil: Int|0); s.cons.head.?('-': Neg(ReadP(Int|0, s.cons.tail)), : ReadP(Int|0, s)); }; };
Fble supports closures, but note that variables are immutable in fble, so you can't use closures to hide mutable state.
For example:
(Int@) { (Int@) { Bool@; }; } IsDouble = (Int@ x) { Int@ 2x = Add(x, x); (Int@ y) { Eq(2x, y); }; }; (Int@) { Bool@; } Is6 = IsDouble(3); (Int@) { Bool@; } Is8 = IsDouble(4);
Function values are always defined anonymously in fble. You can use normal fble function definitions as you would lambda functions in other languages. For example:
List@ sorted = Sort((Record@ x, Record@ y) { Gt(x.id, y.id); }, list);
Fble supports polymorphic data types and functions with straight-forward type inference.
For example:
<@ A@, @ B@>(List@<A@>, (A@) { B@; }) { List@<B@>; } Map = <@ A@, @ B@>(List@<A@> a, (A@) { B@; } f) { a.?(nil: Nil<B@>); Cons(f(a.cons.head), Map(a.cons.tail, f)); }; # With explicit types: List@<Int@> ints = List<Int@>[1, 2, 3, 4]; List@<String@> strings = Map<Int@, String@>(ints, ShowInt); # Using type inference: List@<Int@> ints = List[1, 2, 3, 4]; List@<String@> strings = Map(ints, ShowInt);
Fble is not an object oriented programming language. It does not have classes.
For namespace management you can use fble's modules. For access controls you can use fble's private types. You can create struct values with type and function fields if you want.
Fble is not an object oriented programming language. It does not have classes. You can use composition instead for sharing and reuse of code.
Interfaces can be described in fble using polymorphic types.
For example, here's how you could define an interface that supports object hashing and equality:
<@>@ Hashable@ = <@ T@> { *( (T@, T@) { Bool@; } equals; (T@) { Int@: } hash; ); }; <@ T@>(Hashable@<T@>, List@<T@>, T@) { Bool@; } Contains = <@ T@>(Hashable@<T@> hashable, List@<T@> list, T@ x) { list.?(nil: False); hashable.equals(x, list.cons.head).?(true: True); Contains(hashable, list.cons.tail, x); }; Hashable@<Bool@> BoolHashable = @( equals: /Core/Bool/Eq%.Eq, hash: (Bool@ x) { x.?(true: 1, false: 0); } ); Hashable@<Int@> IntHashable = @( equals: /Core/Int/Eq%.Eq, hash: (Int@ x) { x; } );
Fble does not have builtin support for type classes. You can define an interface as shown in the previous section and pass an explicit implementation of the methods everywhere they would be used.
Fble does not have builtin support for exceptions or error handling.
Library code can be written that leverages function bind syntax to make it easy to write code involving potential error cases. For example:
M@<Command@> ParseCommand = { String@ name <- Do(ParseName); name.?(nil: Error<Command@>(Str|'missing command name')); List@<Markup@> args <- Do(ParseArgs); Return(Command(name, args)); };
Fble has special syntax for applying a function to a function which can be used in place of do-notation. The main difference is the bind function is passed explicitly on each line instead of inferred by the types. For example:
M@<Command@> ParseCommand = { String@ name <- Do(ParseName); name.?(nil: Error<Command@>(Str|'missing command name')); List@<Markup@> args <- Do(ParseArgs); Return(Command(name, args)); };
In this case the functions Do
and Return
are user defined, not part
of the function bind syntax.
Fble has a notion of modules, which allows you to group together and define related types and functions in separate files.
Fble has a separate private type feature that can be used to restrict access to internals of a module or a group of modules.
For example, here's part of the /Core/String%
module, which re-uses code
from the /Core/Char%
and /Core/String%
modules in its
implementation:
@ Char@ = /Core/Char%.Char@; % Chars = /Core/Char%.Chars; <@>@ List@ = /Core/List%.List@; <@>% List = /Core/List%.List; <@>% Cons = /Core/List%.Cons; <@>% Append = /Core/List%.Append; # String@ -- # A character string. @ String@ = List@<Char@>; # StrE -- # A string literal that supports the following escape sequences: # \\ - backslash # \n - newline # \t - tab # # Any other use of \ is undefined. (List@<Char@>) { String@; } StrE = (List@<Char@> chars) { chars.?(nil: chars); chars.cons.head.?('\': { chars.cons.tail.?(nil: /Core/Undef%.Undef<String@>.undefined); String@ tail = StrE(chars.cons.tail.cons.tail); Char@ head = chars.cons.tail.cons.head.?( '\': Chars.'\', 'n': Chars.nl, 't': Chars.tab, : /Core/Undef%.Undef<Char@>.undefined); Cons(head, tail); }); Cons(chars.cons.head, StrE(chars.cons.tail)); }; # Strs -- # Concatenates a list of strings together. # # Example Use: # String@ s = Strs[StrE|'x is ', Show(x), StrE|'.'] (List@<String@>) { String@; } Strs = /Core/List%.Concat<Char@>; @(String@, StrE, Strs);
Fble does not have builtin support for threading or concurrency.
The reference implementation of fble is single threaded. In theory you could develop a multi-threaded implementation to take advantage of multiple cores, but it would need to find its own parallelism in the program.
You can write library code and use function bind to describe programs composed of pieces that appear to run concurrently. The md5 application included in the fble release has an example of this.
Fble does not have a standardized foreign function interface defined yet.
The reference implementation has a C API for manipulating fble values at runtime. This is how standard IO and graphics are implemented in fble. The C API, however, is not yet stable and not standardized as part of the fble language specification.
Head over to the Install
tutorial to get your hands on
fble and try it out.