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}!`
}
fn greet(name: string = "world") -> string {
`Hello, ${name}!`
}

Use fn(x) for inline anonymous functions:

todos |> Array.map(fn(t) t.text)
items |> Array.reduce(fn(acc, x) acc + x.price, 0)
onClick={fn() 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 = fn(x) ... is a compile error. If it has a name, use fn:

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

Use fn and -> to describe function types:

type Transform = fn(string) -> number
type Predicate = fn(Todo) -> boolean
type Callback = fn() -> ()
async fn fetchUser(id: string) -> Promise<User> {
const response = await fetch(`/api/users/${id}`)
await response.json()
}
  • 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 => - use fn(x) for closures, -> for types and match arms

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