Overloads
Calibre supports operator overloading through @overload.
This lets a type customize how certain basic expressions behave.
You declare overloads directly on the type definition.
type Vec2 := struct { x y : int } @overload { const "+" := fn (a : Vec2, b : Vec2) -> Vec2 => Vec2 { x : a.x + b.x, y : a.y + b.y };};In this example, v + w now uses the custom + implementation for Vec2.
let v := Vec2 { x : 1, y : 2 };let w := Vec2 { x : 3, y : 4 };print(v + w);Overloads are written as specially named const items inside the @overload block.
Some of the core operations that can be overloaded include:
- arithmetic operators like
"+" - equality with
"=" - indexing with
"[]" - indexed assignment with
"[]=" - membership with
"in" - conversions with
"as"
For example, equality can be customized:
type Vec2 := struct { x y : int } @overload { const "=" := fn (a b : Vec2) -> bool => a.x = b.x && a.y = b.y;};Now expressions like v = other_vec use this logic.
Indexing can also be overloaded.
type Vec2 := struct { x y : int } @overload { const "[]" := fn (self : Vec2, idx : int) -> int => { if idx = 0 => return self.x; if idx = 1 => return self.y; 0; };};That allows expressions like:
print(v[0]);print(v[1]);Indexed assignment can be overloaded separately with "[]=".
type Vec2 := struct { x y : int } @overload { const "[]=" := fn (self : Vec2, idx : int, value : int) -> Vec2 => { if idx = 0 => self.x := value; if idx = 1 => self.y := value; self; };};This enables syntax like:
let mut v := Vec2 { x : 1, y : 2 };v[0] := 9;The in operator can also be customized.
type Vec2 := struct { x y : int } @overload { const "in" := fn (value : int, vec : Vec2) -> bool => value = vec.x || value = vec.y;};So code like 2 in v can mean whatever membership rule makes sense for the type.
The as operation can be overloaded too.
type Vec2 := struct { x y : int } @overload { const "as" := fn (self : Vec2) -> str => "Vec2";};This makes expressions like v as str use your custom conversion behavior.
Overloads are not just for user-defined structs. They can also be applied to other types.
type int := int @overload { const "as" := fn (self : int) -> str => "int";};Use @overload when you want a type to participate naturally in Calibre expressions like +, [], in, or as,
but keep in mind that overloaded behavior should still feel intuitive to someone reading the code.