Functions & Const
Const Declarations
Section titled “Const Declarations”All bindings are immutable. Use const:
const name = "Floe"const count = 42const active = trueWith type annotations:
const name: string = "Floe"const count: number = 42Destructuring
Section titled “Destructuring”const [first, second] = getItems()const { name, age } = getUser()Functions
Section titled “Functions”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}!`}Generic Functions
Section titled “Generic Functions”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; }Default Parameters
Section titled “Default Parameters”fn greet(name: string = "world") -> string { `Hello, ${name}!`}Anonymous Functions (Closures)
Section titled “Anonymous Functions (Closures)”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 ERRORconst double = (x) => x * 2
// correctfn double(x: number) -> number { x * 2 }Function Types
Section titled “Function Types”Use -> to describe function types:
type Transform = (string) -> numbertype Predicate = (Todo) -> booleantype Callback = () -> ()Async Functions
Section titled “Async Functions”async fn fetchUser(id: string) -> Promise<User> { const response = await fetch(`/api/users/${id}`) await response.json()}Callback Flattening with use
Section titled “Callback Flattening with use”The use keyword flattens nested callbacks. The rest of the block becomes the callback body:
// Without use — deeply nestedFile.open(path, (file) => File.readAll(file, (contents) => contents |> String.toUpper ))
// With use — flat and readableuse file <- File.open(path)use contents <- File.readAll(file)contents |> String.toUpperZero-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).
What’s Not Here
Section titled “What’s Not Here”- No
letorvar- all bindings areconst - 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) => expris only for inline anonymous functions;->is used for function types like(T) -> U - No
functionkeyword - usefnfor named functions
These are removed intentionally. See the introduction for the reasoning.