Skip to main content

Basics

Comments

Comments are used to annotate and document code. All comments start with // and extend to the end of the line.

import std/io

// This function is called when the program is started.
function main() -> () :: IO =
io.println("Hello") // Comments can appear after code.

Documentation comments begin with /// and are used to document the following declaration.

/// Compute the factorial of a given number.
function factorial(n: Int) -> Int = match n
case 0 => 1
case m => m * factorial(m - 1)

Booleans

Booleans are represented by the Bool type, which can take on the values True and False.

True
False

Integers

Integers are represented by the Int type, which is an arbitrary precision signed integer (commonly referred to as a bignum). By default, integers are specified in decimal (base 10). Case-sensitive prefixes of 0b for binary (base 2), 0o for octal (base 8), and 0x for hexadecimal (base 16) may be used. Underscores (_) may appear anywhere in the literal, including before, after, and in between in any digits or prefixes (e.g., 0b and 0_b are both valid prefixes). Multiple underscores may appear consecutively.

0 50 1_000_000                      // Decimal
0b10101010 0b1111_0110 // Binary
0o12345670 0o123_005_774 // Octal
0x1234567890abcdefABCDEF 0xfe_23_06 // Hexadecimal

// Excessive underscores.
_1__2___3____4_____
_0_b_1_0_1_0_

Floats

A Float is an IEEE 754 floating-point number with at least double precision (i.e., 64 bits). A Float literal must include either a . or e (or both) to distinguish it from an integer literal. Like integers, underscores (_) can appear anywhere in the float.

0.0 23.45 1_057.1 3.141_593  // Regular notation.
1e9 2.5e-4 2_712.349_753e+10 // Scientific notation.

// Excessive underscores.
___6___.__7_8__9__e__-__4__5__

Strings

A String represents textual data encoded in UTF-8. String literals use double quotes ("...") and may contain Unicode characters. Escapes are used to insert values that may not be easy or possible to type:

  • \n: Newline (U+000A).
  • \r: Carriage return (U+000D).
  • \t: Tab (U+0009).
  • \\: Backslash (\).
  • \": Double quote (").
  • \u{X...}: Unicode escape where each X is a hexadecimal digit. There must be at least 1 digit. The hexadecimal number is parsed and converted into the corresponding codepoint.
// These strings are equal.
let a = "🍉 is a watermelon";
let b = "\u{1F349} is a watermelon"

When a string spans multiple lines, all lines are joined by a space. Leading whitespace on continuing lines, trailing whitespace on non-terminal lines, and empty lines are ignored. Escapes such as \n and \u{...} are preserved.

// These strings are equal.
let a = "apples
bananas cherries";

let b = "apples bananas cherries"

Block strings use three double quotes to delimit the start and end. The following whitespaces are stripped: leading whitespace before the first non-whitespace line of text, leading whitespace common to the start of each line, trailing whitespace on each line, trailing whitespace after the last non-whitespace line of text, and carriage returns (U+000D) are stripped. Carriage returns included by an escape, such as \r, are respected.

// These strings are equal.
let a = """
text that
is spread
across
several lines
""";

let b = " text that\n is spread\nacross\n several lines"
// -.-. -.-.-.-. -.-.

Tuples

A tuple is an ordered collection of values of arbitrary types. Tuples are created with (...) and may have zero or more elements. The type of a tuple is simply the tuple of the types of each element.

let a: () = (); // 0-tuple.
let b: (Int,) = (3.14,); // 1-tuple, trailing comma is required.
let c: (Bool, String, List(Int)) = (True, "text", [5, 6, 7]) // 3-tuple.

Elements of a tuples can be directly accessed with #, such as c#1 to access the string.

Lists

A List is an ordered collection of values belonging to the same type, implemented as a singly linked list. Lists are created with [...] and may have any length (including zero).

let a: List(Bool) = []; // Empty.
let b: List(Int) = [1, 2, 3];
let c: List(String) = ["one", "two", "three"]

A list can also be created from a different list using a spread. There can be at most one spread, which must appear as the last entry in the list.

let xs = [3, 4];
let ys = [1, 2, ..xs] // [1, 2, 3, 4].

Bindings

A let expression binds names to an irrefutable pattern.

let message = "Hello";
let (x, y, z) = (1, 2, 3)

Expressions

Fuyu is expression-oriented, which means everything is an expression that evaluates to a value.

// This match expression evaluates to a value of type `String`.
match x < 0
case True => "negative"
case False => "non-negative"

Sequencing

Multiple expressions are separated by ;.

import std/io

function main() -> () :: IO =
io.println("Hello.");
io.println("Hello again.")

This can be used within {...} to build a larger expression.

import std/io
import std/math

/// Compute the magnetic field strength of a current carrying wire using
/// B = μ₀I/2πr.
///
/// The wire is assumed to be straight. It is carrying an electric current, I,
/// in Amps (A) and evaluated at a point at a distance, r, in metres (m), from
/// the wire. The result is the magnetic field strength in Teslas (T).
function magnetic_field_strength(i: Float, r: Float) -> Float :: IO =
let b = {
let u0 = 1.256_637e-6; // Vacuum permeability, N⋅A⁻².
u0 * i / (2 * math.pi() * r)
};
// u0 is not in scope out here!
io.println(b);
b

Scope shadowing is not allowed (i.e., introducing a let binding with the same name as a binding in an outer scope).

Functions

Functions are created by defining the name, type, and body.

function fib(n: Int) -> Int = match n
case 0 => 0
case 1 => 1
case m => fib(m - 1) + fib(m - 2)

Precedence

The following table shows all operator and expression types sorted by decreasing precedence (i.e., expressions at the top of the table have higher precedence and are evaluated first).

Operator/ExpressionDescriptionAssociativity
()
[]
{}
Grouping
a.b
a#b
a#0
f(x, y)
Namespace
Record field
Tuple field
Function application
Left-to-right
a ** bExponentiationRight-to-left
-a
..a
Negation
Spread
Right-to-left
a * b
a / b
a % b
Multiplication
Division
Modulo
Left-to-right
a + b
a - b
Addition
Subtraction
Left-to-right
a == b
a /= b
a < b
a <= b
a >= b
a > b
Equality
Inequality
Lesser
Lesser or equal
Greater or equal
Greater
Left-to-right
a /\ bLogical andLeft-to-right
a \/ bLogical orLeft-to-right
\x, y -> ...Anonymous function
a |> f()PipeLeft-to-right
;Expression sequencerLeft-to-right
,SeparatorLeft-to-right
info

Left-to-right associativity, such as +, means that a + b + c is equivalent to (a + b) + c.

Right-to-left associativity, such as **, means that a ** b ** c is equivalent to a ** (b ** c).

Naming rules

Names created in different ways have strict naming rules. These are not simply conventions, as the compiler will reject programs that do not follow these rules.

Name ofExampleRegular Expression
Valuex, some_value_*[a-z][a-z0-9_]*
Functionf, read_all_*[a-z][a-z0-9_]*
TypeList, SomeType_*[A-Z][A-Za-z0-9_]*
TraitAdd, SomeTrait_*[A-Z][A-Za-z0-9_]*
ConstructorSome, None_*[A-Z][A-Za-z0-9_]*
Type parametera, type_param_*[a-z][a-z0-9_]*
Modulestd, some_module_*[a-z][a-z0-9_]*
Sink_, ____+

Type and constructor names that have aconyms or initialisms should only capitalize the first letter of the acronym or initialism. For example:

  • HtmlParser is the right way to do it.
  • HTMLParser is bad.

The sink is a write-only name, and is used to note a value that must be bound but is not used.