Capabilities: Pure functions
● Intermediate2 min
A function without uses is pure: it can't affect the outside world at all.
pub fn price_with_tax(price: Decimal) -> Decimal {
debug("working out tax on {price}")
price * 1.18
}
fn main() {
print(price_with_tax(100.00))
}The program ran and printed:
kavach run cap-pure.kav
118.0000- A pure function gives the same result for the same arguments, every time. That makes it easy to test.
debug()needs no capability: it writes to the error output, and release builds leave it out.- Logging with
std.logneeds none either: the program's owner decides where log events go.
Functions given as values
A function value carries the rights of the code that made it. Naming a function as a value, or writing a lambda, needs what that function uses, as calling it would. Calling a function value needs nothing more:
fn twice(f: fn(Int) -> Int, n: Int) -> Int {
f(f(n))
}
pub fn shout(n: Int) -> Int uses Console {
const loud = x => {
print("got {x}")
x + 1
}
twice(loud, n)
}
fn main() {
print(shout(1))
}The program ran and printed:
kavach run cap-fn-values.kav
got 1got 23
twicestays pure: it only calls what it is given.shoutwrites the lambda that prints, soshoutneedsConsole.- So a library can call the functions it is given, as a web framework calls its handlers, without being granted what they use.
A function type can set a limit with uses: a function value may then use only what the type lists.
use std.net
fn fetch_count(n: Int) -> Int uses Net {
n
}
fn run_quietly(f: fn(Int) -> Int uses Console, n: Int) -> Int {
f(n)
}
fn main() {
print(run_quietly(fetch_count, 1))
}The compiler refused the program:
kavach run cap-fn-type.kav
error: `fetch_count` uses Net, but the function type it is given as allows only Console (spec 3.4)