Skip to main content

Functions

This page covers function definitions, calling conventions, signatures, and advanced patterns in TypR.

Defining typed functions

The fn keyword defines a typed function. Parameter types use : type, and the return type appears after the closing parenthesis:

let add <- fn(a: int, b: int): int {
a + b
};

print(add(5, 3));
caution

fn(...) always requires a return type — omitting it triggers an explicit panic ("You forgot to specify the function return type").


Default parameters

Default values are supported for the final parameter(s):

let greet <- fn(name: char, greeting: char = "Hello"): char {
greeting
};

greet("World"); # "Hello"
greet("World", "Hi"); # "Hi"

Variadic functions

let sum_all <- fn(...xs: int): int {
/* ... */
};

The ... prefix makes a parameter accept any number of arguments.


Calling conventions

TypR supports three equivalent ways to call a function, thanks to the uniform function call syntax (UFCS):

# Classic call
print(add(5, 3));

# Pipe syntax (|>)
(5) |> add(3)
|> print();

# Method call syntax (.)
(5).add(3)
.print();

All three styles produce the same result. The first argument can be "pulled out" as the receiver in pipe or method-call notation — making operation chaining readable and natural.


Lambdas

Lambdas use \ without type annotations:

let sq <- \(x) x * x;
info

\(...) (lambda) has no declared parameter types or return type. Use fn(...) when types are needed.


Partial application

The \ symbol also supports partial application of functions:

let add5 <- \add(a = 5);                         # partial application of a function
let origin <- \Point:{ x = 0, y = 0 }; # partial application of a record constructor
note

Lambda vs partial application: both use \, disambiguating by what follows — \( → lambda, \identifier( → partial application. PartialApp is desugared into Lang::Function during type-checking and never reaches transpilation.


Higher-order functions

Functions are first-class values in TypR. They can be passed as arguments and returned as values:

type Function <- (int) -> bool;

let function0 <- fn(a: int): bool {
true
};

The type system tracks function types using (T1, T2) -> T3 syntax, ensuring composition and callbacks are type-safe.


Closures

Functions can capture variables from their surrounding environment. The type system ensures captured variables and returned functions remain type-safe:

let make_adder <- fn(n: int): (int) -> int {
fn(x: int): int { x + n }
};

let add5 <- make_adder(5);
add5(3); # 8

Signatures: typing existing R functions

By default, most base R functions accept Any and return Empty. The @ annotation declares the type of an existing R function without modifying it:

@toupper: (char) -> char;

toupper("Hi"); # now takes char, returns char
# toupper(7); # would now fail at compile time

See Signatures, @extern & Foreign for overloading, @extern, and @importFrom.


Interfaces: polymorphic functions

Interfaces enable ad-hoc polymorphism — write functions that work across multiple types:

@paste: (Any, Any) -> char;

type Viewable <- interface {
view: (Self) -> char
};

let double <- fn(a: Viewable): char {
paste(view(a), view(a))
};

let view <- fn(a: bool): char { "bool" };

true.double() # works because bool implements Viewable

See Interfaces & Structural Validation for details.