Functions and Closures
Karina supports closure and function types as a powerful way to define and work with anonymous functions. These functions are treated as interfaces under the hood, allowing for rich type inference, interface implementation, and seamless integration into the Java type system.
Defining Anonymous Functions
Anonymous functions in Karina are defined using the fn
keyword. Every anonymous function is, in essence, a class that implements one or more interfaces.
Here's an example where the function explicitly implements an interfaces:
// fn(a: bool) -> void
fn (a) impl Consumer<Bool>, Setter<Bool> {
println(a)
}
- Explicit Interface Implementation: You can specify the underlying interfaces your function should implement using the
impl
keyword. This is useful when the interface cannot be inferred automatically. - Multiple Interfaces: Functions can implement more than one interface, providing flexibility in how they interact with the type system.
Implicit Interface Creation
If no interface is explicitly defined, Karina will infer the function's type and automatically use an existing interface during compilation. For instance:
// fn(int, bool) -> bool
let some = fn (a: int, b) {
b
}
let other = !some()
What Happens Here:
- The type of
a
is explicitly defined asint
, whileb
's type is inferred. - The return type of the function is the same as the parameter
b
. - When using the NOT operator
!
on the function, abool
is expected as the return type of the function. As the return type of the function is inferred asbool
and the return type is the same as the parameterb
, the parameterb
is also inferred asbool
. - Since no interface is specified, Karina will default to a default interface (karina.lang.function.Function2_1) that matches the function's signature. Some build in interfaces extend common java interfaces, so they can be used in some java functions, when the interface cannot be inferred automatically.
import java.util.function.UnaryOperator
fn call() {
// fn(bool) -> bool impl UnaryOperator<bool>, karina.lang.function.Function1_1<bool, bool>
operate(fn (x: bool) {
return !bool
}, 1)
//UnaryOperator<bool> is inferred as the interface from the operate function.
//karina.lang.function.Function1_1 is the default interface for one parameter and one return type.
}
fn operate<T>(function: UnaryOperator<T>, element: T) -> T {
function.apply(element)
}
Optional Parameter and Return Types
In Karina, parameter types and return types can be omitted if they are inferable:
// fn (int) -> int
let square = fn (x) {
x * x
}
let result = [1, 2, 3].map(square)
In this example:
- The type of
x
is inferred based on how the function is used. - The return type is deduced as the same type of
x
.
Karina ensures that every function can fulfill its role in multiple contexts by allowing it to implement multiple interfaces. You can these functions in java code and Karina code almost seamlessly.