Skip to content

Functions

greet = => "Hello!" ~ no params
double = (x :: Num) => x * 2 ~ one param
add = (a :: Num, b :: Num) => a + b ~ multiple params
typed = (a :: Num, b :: Num) -> Num => a + b

Every function parameter must be annotated — there is no default type; an unannotated parameter is a compile error that names it. There are two exceptions. A lambda passed to a built-in collection method (.map / .filter / .reduce / .each) takes its parameter type from the element type of the receiver. And an unannotated method parameter defaults to Num (see named record types). Multi-statement bodies use < > blocks (the last expression is the value):

compute = (x :: Num) => <
doubled = x * 2
doubled * doubled
>

Functions may recurse; a recursive function needs a -> Type annotation:

factorial = (n :: Num) -> Num => n == 0 ? 1 : n * factorial(n - 1)

(See examples/factorial.qn, examples/fibonacci.qn.)

A function type is written with the arrow, reusing ->. The parameter types go in parentheses; $ (Unit) names a function that returns nothing:

() -> $ ~ takes nothing, returns unit
(Num) -> Bool ~ one parameter
(Num, Text) -> Bool ~ two parameters

A function type may be a parameter type, which is what makes a function higher-order — it takes another function as an argument and calls it:

apply = (f :: (Num) -> Num, x :: Num) -> Num => f(x)
twice = (f :: (Num) -> Num, x :: Num) -> Num => f(f(x))
^ = () -> Num => twice((n :: Num) => n * 2, 3) ~ ((3*2)*2) = 12

The value passed in is a closure — a lambda literal (as above) or a named closure passed by its name. Function types may nest as parameter types (((Num) -> Bool, Num) -> Bool). A function-typed return (currying, (A) -> (B) -> C) is not supported yet. (See examples/higher_order.qn.)

A call may only name something already defined above it — there is no hoisting. A definition is in scope for its own body (so a function may recurse) and for everything that follows it, but not for anything before it:

^ = () -> Num => later() ~ error: Undefined variable 'later'
later = () -> Num => 7

This holds for overload-set members too, which report the situation by name:

h = () -> Text => g(1) ~ error: cannot call 'g' before its definition
g = (n :: Num) -> Text => "a"
g = (t :: Text) -> Text => "b"

So mutual recursion between top-level functions is not expressible: whichever of the pair comes first would have to call the other before it exists. Self-recursion is unaffected, including a recursive overload member calling itself. Restructure a mutual pair into one self-recursive function.