Implementing a Contract
Simply defining a contract is not sufficient to actually be useful, however, since the definition itself doesn't provide any logic. So, to actually use a Contract, we must implement it for a certain (set of) concrete type(s):
Fig 1:
implement Operators<int> {
function add(lhs: int, rhs: int) -> int {
return lhs + rhs;
}
}
implement Operators<string> {
function add(lhs: string, rhs: string) -> string {
return "{lhs}{rhs}";
}
}
Now that you have implementations, you can either call them directly:
Fig 2:
print(Operators::add(10, 20));
print(Operators::add("Hello, ", "world"));
Output:
30
Hello, world
Or, even more valuable, you can also call the generic sum
function from the
previous section over concrete types int
or string
because the requirements are
met for both!
Fig 3:
print(sum([1, 2, 3]));
print(sum(["a", "bc", "d"]));
Output:
6
abcd
In this way, Claro's Contracts interact with Generics to create a powerful form of code reuse where custom behavior can be uniquely dictated by type information. And, unlike in an Object-Oriented language, this code reuse did not rely on creating any subtyping relationships.
Static Enforcement of requires(...)
Clauses
Of course, if you attempted to call a generic procedure that requires some contract(s) to be implemented, a compilation error will be triggered if the contract was not actually implemented.
Fig 4:
# Operators<double> hasn't been implemented, so this call will be rejected.
print(sum([1.0, 2.0, 3.0]));
Compilation Errors:
Invalid Generic Procedure Call: For the call to the following generic procedure `sum` with the following signature:
`function<[T] -> T> Generic Over {T} Requiring Impls for Contracts {Operators$<T>}`
No implementation of the required contract Operators$<double>.
1 Error
Note: Claro's error messaging is a work in progress - the above error message will be improved.
A Note on Static Dispatch via "Monomorphization"
As a performance note - even beyond the conceptual simplification benefits of avoiding dependence on subtyping
relationships to achieve custom behaviors, Claro also achieves performance gains through its ability at compile-time to
statically know which custom Contract implementation will be called. In the Object-Oriented approach, generally
speaking the procedure receiving an arg of an interface type doesn't know which particular implementation will be called
at runtime. This leads to the situation where a runtime "dispatch table"/"vtable" lookup is required to determine which
particular implementation to call for each particular value passed into the procedure. Claro is a "monomorphizing"
compiler, meaning that during compilation each Generic Procedure has a customized implementation codegen'd for each set
of concrete types the procedure is actually called with. In this way, there's no runtime dispatch overhead when types
are statically known (which is always true unless you're explicitly calling a generic procedure over a oneof<...>
type - but in this case you're consciously opting into dynamic dispatch overhead).