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).