Evaluated declarations are actually inline expressions, which can be created using the following grammar:
Not only that, the value of the evaluated expressions will also be shown on STDOUT. However, evaluated declarations will only be evaluated if they are written in the entry file, except for module imports.
For example, if we interpret the following Keli program,we shall see 120 on STDOUT.
5.1 Constant declarations
Constant declarations are useful for defining common constants such as the value of pi, e, etc. They can be declared using the following grammar:
In the code above, the first this is the parameter for the function square , where this should be type of Int. The body of this function is this.*(this) , while the return type of this function is also Int.
Return type annotation for functions are optional (since it can be inferred), so the square function can be rewritten as :
5.2.2 Polyfunc declarations
Polyfunc can be created using the following grammar:
Same as unifunc, the return type annotation of polyfunc declaration is also optional.
5.2.3 Multiple dispatch
Keli supports multiple dispatch (a.k.a function overloading), thus it is possible to declare multiple function that have the same set of identifiers as long as their parameters type does not fully match.
For example, the following Keli code is valid:
(x Int).+(y Int)=undefined
(x Float).+(y Float)=undefined
(x Int).+(y Float)= undefined
Moreover, functions with overlapping identifiers are also permitted:
typeVarId is any valid identifiers, while typeConstraint is any valid constraint expressions.
For example, the identity function can be defined as such:
(this T).identity | T = this
In the code above, T is the type variable identifier, while Any is the constraint on T , Any also means no constraint.
The type variable T is inferred using some sort of Hindley-Milner type inference system.
x =123.identity // the type of `x` is inferred as `Int`
y ="Hello".identity // the type of `y` is inferred as `String`
Generic function by itself is not too useful unless incorporated with generic types such as generic objects or generic tagged union.
5.2.5 Function specialization
Function specialization is a phenomenon where multiple dispatch and generic functions are used in synergy. This feature allows a function to be specialized when specified, and generic when unspecified.
Although normal user will rarely utilize this feature, it is crucial for library author to write generic modules.
A common example is the toString function. It is a generic function, however, it may be specialized, as such:
// generic version
(this A).toString =
// specialized for integer
(this Int).toString =
"I'm an integer"
="Hello".toString // "Hello"
=123.toString // "I'm an integer"
=1.0.toString // "1.0"
Documentation for functions can be created using string expressions instead of comments. They can optionally appear:
before the function declaration
after each parameter
after return type annotation
"Slices list from startIndex until including endIndex"
(this List.of(A))"The list to be sliced."
from(startIndex Int)"Zero-based index."
to(endIndex Int)"Zero-based index. Inclusive."
| List.of(A)"Return a new list."
5.3 Object type alias
Object type alias (a.k.a struct types) can be created using the following grammar:
unionId should follow the PascalCase convention. tagDecl is either a carryless tag or a carryful tag.
unionId can be used as tag constructor prefix or type annotation.
5.4.3 Union name as tag constructor prefix
For example, we can use the identifier Shape to create carryless tag and carryful tag.
x = Shape.None
y = Shape.Circle($.radius(3.2))
The type of x and y are both Shape .
5.4.4 Union name as type annotation
For example, we can use the identifier Color as function parameter type annotation.
(this Shape).area | Float = undefined
5.5 Type constructor declarations
Type constructors (a.k.a generic types) are actually function that takes one or more types and return a new type. In Keli, there are 2 kinds of type constructor, namely object type constructor and tagged union type constructor.
5.5.1 Object type constructor
Object type constructor can be declared using the following grammar:
where funcSignature is either a unifuncSignature or _[_polyfuncSignature](section-5-declarations.md#5-2-2-polyfunc-declarations).
To defined required function, the return type annotation cannot be omitted like usual functions do.
For example, the code below is saying that if a data type is Comparable , then it must have the == function and > function defined.
(this A).==(that A)| Boolean = required
(this A).>(that A)| Boolean = required
The set of required functions can only be defined within the module where the interface name is defined.
5.6.3 Interface usage
Unlike object-oriented languages, where interface identifiers can be used as type annotation, interface identifier can only be used as type constraint annotations. Thus, they can only appear in generic functions or generic types.