Skip to content

Functions & Const

All bindings are immutable. Use const:

const name = "Floe"
const count = 42
const active = true

With type annotations:

const name: string = "Floe"
const count: number = 42
const [first, second] = getItems()
const { name, age } = getUser()
fn add(a: number, b: number) -> number {
a + b
}

The last expression in a function body is the return value. The return keyword is not used in Floe.

In multi-statement functions, floe fmt adds a blank line before the final expression to visually separate the return value:

fn loadProfile(id: string) -> Result<Profile, ApiError> {
const user = fetchUser(id)?
const posts = fetchPosts(user.id)?
const stats = computeStats(posts)
Profile(user, posts, stats)
}

Exported functions must have return type annotations:

export fn greet(name: string) -> string {
`Hello, ${name}!`
}

Functions can declare type parameters using angle brackets after the function name:

fn identity<T>(x: T) -> T { x }
fn pair<A, B>(a: A, b: B) -> (A, B) { (a, b) }
fn mapResult<T, U, E>(r: Result<T, E>, f: (T) -> U) -> Result<U, E> {
match r {
Ok(value) -> Ok(f(value)),
Err(e) -> Err(e),
}
}

Generic functions compile directly to TypeScript generics:

function identity<T>(x: T): T { return x; }
function pair<A, B>(a: A, b: B): readonly [A, B] { return [a, b] as const; }
fn greet(name: string = "world") -> string {
`Hello, ${name}!`
}

Use (x) => expr for inline anonymous functions:

todos |> Array.map((t) => t.text)
items |> Array.reduce((acc, x) => acc + x.price, 0)
onClick={() => setCount(count + 1)}

For simple field access, use dot shorthand:

todos |> Array.filter(.done == false)
todos |> Array.map(.text)
users |> Array.sortBy(.name)

const name = (x) => ... is a compile error. If it has a name, use fn:

// COMPILE ERROR
const double = (x) => x * 2
// correct
fn double(x: number) -> number { x * 2 }

Use -> to describe function types:

type Transform = (string) -> number
type Predicate = (Todo) -> boolean
type Callback = () -> ()
async fn fetchUser(id: string) -> Promise<User> {
const response = await fetch(`/api/users/${id}`)
await response.json()
}

The use keyword flattens nested callbacks. The rest of the block becomes the callback body:

// Without use — deeply nested
File.open(path, (file) =>
File.readAll(file, (contents) =>
contents |> String.toUpper
)
)
// With use — flat and readable
use file <- File.open(path)
use contents <- File.readAll(file)
contents |> String.toUpper

Zero-binding form for callbacks that don’t pass a value:

use <- Timer.delay(1000)
Console.log("step 1")
use <- Timer.delay(500)
Console.log("done")

use works with any function whose last parameter is a callback. It’s complementary to ? (which only works on Result/Option).

  • No let or var - all bindings are const
  • No class - use functions and records
  • No this - functions are pure by default
  • No function* generators - use arrays and pipes
  • No => at the statement level - (x) => expr is only for inline anonymous functions; -> is used for function types like (T) -> U
  • No function keyword - use fn for named functions

These are removed intentionally. See the introduction for the reasoning.