4.1. Functions
Overview of Letlang's function declarations
A function is a sequence of propositions (let
expressions, or expressions)
always returning a value.
It may take type parameters, or call parameters. The value returned by the last proposition is the return value of the function.
NB: The only form of early return possible is by throwing an exception.
Type checking happens at the function boundaries:
Syntax:
<function-statement> :=
[ "pub" ] "func" <identifier>
[ <function-type-params> ]
"(" [ <function-call-params> ] ")"
"->" <type-ref> "{"
<proposition>+
"}"
;
<function-type-params> :=
"<" <identifier> ("," <identifier>)* ">"
;
<function-call-params> :=
<function-call-param> ("," <function-call-param>)*
;
<function-call-param> :=
<identifier> ":" <type-ref>
;
Simple pure function:
func adder(a: number, b: number) -> number {
a + b;
}
Simple pure generic function:
func swap<T, U>(a: T, b: U) -> (U, T) {
(b, a);
}
Function with side effects:
effect log(msg: string) -> @ok;
func task() -> @ok {
@ok := perform log("hello");
@ok := perform log("world");
}
Early return with exceptions:
import std::math;
func task(a: number) -> @ok {
cond {
a >= 0 => {
std::math::sqrt(a);
},
else => {
throw (@error, "no complex numbers here");
},
};
}
A function is a structure implementing the builtin trait Function
.
The structure’s members holds the type parameters. Calling the function results
in instantiating this structure and calling the call()
function from the
Function
trait:
func foo() -> @ok {
# do stuff
@ok;
}
Is translated to:
mod symbol_foo {
struct func_foo;
impl Function for func_foo {
fn call(
&self,
context: &mut TaskContext,
args: Vec<Value>,
) -> FunctionContinuation {
// 1. type check arguments
// 2. execute function's body
}
}
}
The call()
function itself is a generator, implemented with the Rust crate
genawaiter (because generators are not
stable yet).
When calling the function, the runtime will execute the generator until completion, side effects and exceptions interrupts the function execution:
let task_args: Vec<Value> = vec![];
let mut block = func.call(&mut context, task_args);
let ignored = Value::Boolean(bool::default());
let mut state = block.resume_with(ignored);
loop {
match &state {
GeneratorState::Yielded(FunctionInterruption::Effect { name, args }) => {
// try to handle side effect
if unknown_side_effect {
// print debug information
return Err(RuntimeError::EffectNotImplemented);
}
else {
let effect_return_value = { /* ... */ };
state = block.resume_with(effect_return_value);
}
},
GeneratorState::Yielded(FunctionInterruption::Exception(exc)) => {
// print exception
return Err(RuntimeError::UncaughtException);
},
GeneratorState::Complete(val) => {
// type check function's return value
break;
}
}
}