Overview

An overview of the Fuse programming language.

Fuse is a statically typed, purely functional language that compiles to native code through GRIN and LLVM. This overview walks through the language from the basics to advanced features.

Hello World

Every Fuse program starts with a main function that returns an i32 exit code.

fun main() -> i32
    print("Hello, World!\n")
    0

The print function outputs a string to standard output. The last expression in the function body is the return value. Here, 0 indicates successful execution.

You can define helper functions and call them from main:

fun println(s: str) -> Unit
    print(s)
    print("\n")
    ()

fun main() -> i32
    println("Hello from Fuse!")
    0

Fuse uses indentation to define blocks. No braces or end keywords are needed. The Unit type (and its value ()) represents “no meaningful value”, similar to void in other languages.

Primitives

Fuse provides a small set of primitive types:

TypeDescription
i3232-bit signed integer
f3232-bit floating point
strString
boolBoolean (true or false)
UnitUnit type (value is ())

Let bindings

Use let to bind a value to a name:

fun main() -> i32
    let x = 5
    let y = x + 1
    let name = "Fuse"
    let pi = 3.14
    let active = true
    0

Operators

fun main() -> i32
    let sum = 1 + 1          # addition
    let diff = 5 - 3         # subtraction
    let product = 4 * 3      # multiplication
    let quotient = 10 / 2    # division
    let remainder = 7 % 3    # modulo
    0

Strings can be concatenated with +:

fun main() -> i32
    let greeting = "Hello " + "World"
    print(greeting)
    0

Comparison and logical operators:

fun main() -> i32
    let a = 1 == 2     # false
    let b = 1 != 2     # true
    let c = 3 < 5      # true
    let d = true && false   # false
    let e = true || false   # true
    0

Algebraic data types

Fuse supports three kinds of algebraic data types: sum types (variants), records (product types with named fields), and tuples (product types with positional fields).

Sum types

Sum types let you define a type that can be one of several variants:

type Color:
    Red
    Green
    Blue

Variants can carry data, and types can be parameterized with type variables:

type Option[T]:
    None
    Some(T)

Types can refer to themselves, enabling structures like linked lists:

type List[A]:
    Cons(h: A, t: List[A])
    Nil

Records

Records are product types where each field has a name:

type Point:
    x: i32
    y: i32

Records can have type parameters:

type Map[K, V]:
    key: K
    value: V

Tuples

Tuples are product types with unnamed, positional fields:

type Pair(i32, str)

type Tuple[A, B](A, B)

Type aliases

You can create type aliases for convenience:

type List[A]:
    Cons(h: A, t: List[A])
    Nil

type Ints = List[i32]

Functions

Functions are defined with the fun keyword, followed by the name, parameters with types, and a return type.

fun sum(x: i32, y: i32) -> i32
    x + y

fun main() -> i32
    let result = sum(5, 3)
    print(int_to_str(result))
    0

Recursive functions

fun fib(n: i32, a: i32, b: i32) -> i32
    match n:
        0 => b
        _ => fib(n - 1, b, a + b)

fun main() -> i32
    let result = fib(10, 0, 1)
    print(int_to_str(result))
    0

Lambda expressions

Anonymous functions use the => arrow syntax:

fun main() -> i32
    let f = a => a + 1
    let g = (x, y) => x + y
    print(int_to_str(f(5)))
    0

Lambdas are first-class values. They can be passed to functions, stored in variables, and returned from functions.

Function types

The type of a function is written with ->:

Pattern Matching

Pattern matching is a core feature of Fuse, used to inspect and destructure values.

Matching on literals

fun describe(x: i32) -> str
    match x:
        1 => "one"
        2 => "two"
        _ => "something else"

fun main() -> i32
    print(describe(2))
    0

The _ pattern matches anything and is used as a catch-all.

Matching on variants

type Option[A]:
    None
    Some(A)

fun describe_option(o: Option[i32]) -> str
    match o:
        Some(v) => "has value: " + int_to_str(v)
        None => "empty"

fun main() -> i32
    let o = Some(42)
    print(describe_option(o))
    0

Inline blocks

When a match arm needs multiple expressions, use braces:

type Option[A]:
    None
    Some(A)

fun main() -> i32
    let o = Some(5)
    match o:
        Some(v) => {
            print("found: ")
            print(int_to_str(v))
            0
        }
        None => 1

Generics

Generics let you write code that works with any type. Fuse uses monomorphization: at compile time, generic code is specialized into concrete versions for each type it’s used with, so there is no runtime overhead.

type Option[A]:
    None
    Some(A)

fun is_some[A](o: Option[A]) -> bool
    match o:
        Some(v) => true
        _ => false

fun main() -> i32
    let x = Some(42)
    match is_some(x):
        true => 0
        false => 1

In most cases, Fuse’s bidirectional type inference figures out the types automatically.

Traits

Traits (also known as type classes) define shared behavior that types can implement. They enable ad-hoc polymorphism, allowing different types to provide their own implementations of the same interface.

trait Functor[A]:
    fun map[B](self, f: A -> B) -> Self[B];

Methods ending with ; are required and must be provided by implementors. Use impl Trait for Type to provide an implementation:

type List[A]:
    Cons(h: A, t: List[A])
    Nil

impl List[A]:
    fun fold_right[A, B](as: List[A], z: B, f: (A, B) -> B) -> B
        match as:
            Cons(x, xs) => f(x, List::fold_right(xs, z, f))
            Nil => z

trait Functor[A]:
    fun map[B](self, f: A -> B) -> Self[B];

impl Functor[A] for List[A]:
    fun map[B](self, f: A -> B) -> List[B]
        List::fold_right(self, Nil[B], (h, t) => Cons(f(h), t))

fun fmap[F: Functor, A, B](x: F[A], f: A -> B) -> F[B]
    x.map(f)

fun main() -> i32
    let l = Cons(1, Cons(2, Cons(3, Nil)))
    let l1 = fmap(l, v => v + 1)
    0

Default implementations

Trait methods can have default implementations:

type Option[A]:
    None
    Some(A)

trait Monad[A]:
    fun unit[T](a: T) -> Self[T];

    fun flat_map[B](self, f: A -> Self[B]) -> Self[B];

    fun map[B](self, f: A -> B) -> Self[B]
        let f = a => Self::unit(f(a))
        self.flat_map(f)

impl Monad for Option[A]:
    fun unit[T](a: T) -> Option[T]
        Some(a)

    fun flat_map[B](self, f: A -> Option[B]) -> Option[B]
        match self:
            Some(v) => f(v)
            _ => None

The Option implementation only needs to provide unit and flat_map. The map method is inherited from the default.

Trait bounds

You can constrain generic type parameters to require a trait implementation using the T: Trait syntax. This ensures the function can only be called with types that implement the specified trait.

trait Summary:
    fun summarize(self) -> str;

type Tweet:
    username: str
    content: str

impl Summary for Tweet:
    fun summarize(self) -> str
        self.username + ": " + self.content

fun notify[T: Summary](s: T) -> Unit
    print("Breaking news! " + s.summarize())

fun main() -> i32
    let tweet = Tweet("elon", "work!")
    notify(tweet)
    0

The [T: Summary] syntax means T must implement the Summary trait. Calling notify with a type that doesn’t implement Summary results in a compile-time error.

Fuse includes built-in traits like Add for arithmetic operators:

fun add[T: Add](a: T, b: T) -> T
    a + b

fun main() -> i32
    let result = add(2, 3)
    print(int_to_str(result))
    0

You can also combine multiple bounds with +, requiring the type to implement all listed traits:

trait Summary:
    fun summarize(self) -> str;

trait Display:
    fun display(self) -> str;

fun to_str[T: Summary + Display](s: T) -> str
    s.display()

Higher-Order Functions

In Fuse, functions are first-class values. They can be passed as arguments, returned from other functions, and stored in variables.

fun apply(x: i32, f: i32 -> i32) -> i32
    f(x)

fun main() -> i32
    let result = apply(5, a => a + 1)
    print(int_to_str(result))
    0

Closures

Closures are anonymous functions that can reference themselves recursively. When combined with impl blocks, they enable expressive iteration patterns:

type List[A]:
    Cons(h: A, t: List[A])
    Nil

impl List[A]:
    fun map[B](self, f: A -> B) -> List[B]
        let iter = (l: List[A], acc: List[B]) => {
            match l:
                Cons(h, t) => iter(t, Cons(f(h), acc))
                Nil => acc
        }
        iter(self, Nil[B])

fun main() -> i32
    let l = Cons(2, Cons(3, Nil))
    let l1 = l.map(v => v + 1)
    let result = {
        match l1:
            Cons(h, t) => h
            Nil => 0
    }
    print(int_to_str(result))
    0

The closure iter calls itself recursively to traverse the list, accumulating results. The closure v => v + 1 passed to map is applied to each element. Closures can be both simple lambdas and recursive local functions.

Map, filter, and fold

type List[A]:
    Cons(h: A, t: List[A])
    Nil

impl List[A]:
    fun fold_right[A, B](as: List[A], z: B, f: (A, B) -> B) -> B
        match as:
            Cons(x, xs) => f(x, List::fold_right(xs, z, f))
            Nil => z

    fun filter[A](self, f: A -> bool) -> List[A]
        List::fold_right(self, Nil[A], (h, t) => {
            match f(h):
                true => Cons(h, t)
                false => t
        })

    fun sum(l: List[i32]) -> i32
        List::fold_right(l, 0, (acc, b) => acc + b)

trait Functor[A]:
    fun map[B](self, f: A -> B) -> Self[B];

impl Functor[A] for List[A]:
    fun map[B](self, f: A -> B) -> List[B]
        List::fold_right(self, Nil[B], (h, t) => Cons(f(h), t))

fun main() -> i32
    let l = Cons(2, Cons(3, Nil))
    let result = l.map(v => v + 1).filter(e => e > 3)
    let s = List::sum(result)
    print(int_to_str(s))
    0

Methods

Methods are functions attached to a type via impl blocks.

Instance methods

Methods that take self as their first parameter are called with dot syntax:

type Option[A]:
    None
    Some(A)

impl Option[A]:
    fun map[B](self, f: A -> B) -> Option[B]
        match self:
            Some(v) => Some(f(v))
            _ => None

fun main() -> i32
    let o = Some(5)
    let o1 = o.map(a => a + 1)
    match o1:
        Some(v) => {
            print(int_to_str(v))
            0
        }
        None => 1

Static methods

Methods without self are static and are called using Type::method() syntax:

type List[A]:
    Cons(h: A, t: List[A])
    Nil

impl List[A]:
    fun fold_right[A, B](as: List[A], z: B, f: (A, B) -> B) -> B
        match as:
            Cons(x, xs) => f(x, List::fold_right(xs, z, f))
            Nil => z

    fun append[A](l1: List[A], l2: List[A]) -> List[A]
        List::fold_right(l1, l2, (h, t) => Cons(h, t))

fun main() -> i32
    let a = Cons(1, Cons(2, Nil))
    let b = Cons(3, Nil)
    let c = List::append(a, b)
    0

Do Notation

Do notation provides syntactic sugar for chaining operations that may fail or produce effects. It desugars into calls to flat_map.

type Option[A]:
    None
    Some(A)

trait Monad[A]:
    fun unit[T](a: T) -> Self[T];

    fun flat_map[B](self, f: A -> Self[B]) -> Self[B];

    fun map[B](self, f: A -> B) -> Self[B]
        let f = a => Self::unit(f(a))
        self.flat_map(f)

impl Monad for Option[A]:
    fun unit[T](a: T) -> Option[T]
        Some(a)

    fun flat_map[B](self, f: A -> Option[B]) -> Option[B]
        match self:
            Some(v) => f(v)
            _ => None

fun main() -> i32
    let x = Some(1)
    let y = Some(2)
    let z = Some(3)
    let result = {
        do:
            i <- x
            j <- y
            k <- z
            i + j + k
    }
    match result:
        Some(v) => v
        _ => 0

The <- operator binds the value inside a monadic context. If any step produces None, the whole chain short-circuits to None.