Interfaces and extensions
Interfaces and extensions separate an operation’s contract from its implementation. They are useful for generic algorithms, methods on types you do not own, operator capabilities, and multiple explicit providers.
Declare an interface
Section titled “Declare an interface”An interface has an implicit subject type named Self:
interface IMeasure method magnitude(self : Self) -> IntendMembers marked method participate in dot-call lookup. An ordinary func
member remains a dictionary function and is called through a provider value.
The distinction matters most for open types, where
only interface method members become dynamically dispatched slots.
Implement it with an extension
Section titled “Implement it with an extension”interface IMeasure method magnitude(self : Self) -> Intend
datatype Distance extensions DistanceMeasure Distance(Int)end
extension DistanceMeasure : IMeasure for Distance method magnitude(distance : Distance) -> Int do distance._0 endend
func main() -> Unit do distance = Distance::Distance(12); Debug.trace(distance.magnitude());endThe extension must satisfy the interface member names and types after Self
is replaced with Distance.
Associated extensions
Section titled “Associated extensions”The datatype’s extensions DistanceMeasure clause associates that provider
with the nominal Distance type. Associated providers are considered when a
method or contextual capability is requested for that owner.
Association exposes provider metadata, not an ordinary source name. Keep an associated extension private unless callers also need to name that specific provider explicitly.
Contextual generic capabilities
Section titled “Contextual generic capabilities”interface IMeasure method magnitude(self : Self) -> Intend
datatype Distance extensions DistanceMeasure Distance(Int)end
extension DistanceMeasure : IMeasure for Distance method magnitude(distance : Distance) -> Int do distance._0 endend
func measure[T]( value : T, ?{extension Measure: IMeasure for T},) -> Int do Measure.magnitude(value)end
func main() -> Unit do distance = Distance::Distance(12); Debug.trace(measure(distance));endAt measure(distance), the compiler finds the associated
DistanceMeasure. Exactly one applicable provider is required. No provider is
a missing-extension error; several equally applicable providers are an
ambiguous-extension error.
Direct extensions
Section titled “Direct extensions”An extension without an interface can add methods directly to a subject type:
extension IntParity for Int method is_even(value : Int) -> Bool do value % 2 == 0 endend
func main() -> Unit do Debug.trace(42.is_even());endOnly method declarations participate in dot-call lookup. A direct
extension’s ordinary func members are callable through its provider value,
not as methods.
For the full func/method comparison, including receiver rules, open-type
dispatch, and value-level witnesses, see Open types.
Method lookup and ordinary fields
Section titled “Method lookup and ordinary fields”For receiver.name(arguments), a directly callable record field takes
priority. Otherwise, the compiler searches visible and associated extension
providers for a unique method whose receiver matches. Provider selection does
not use the expected result type to choose between ambiguous providers.
An exported extension from another module is not automatically active merely
because that module is visible. Adopt an unassociated provider with
with extensions Module.Extension do ... end around the smallest expression
or function body that owns the policy. Non-associated declarations in the same
module are inert as well. A private extension that is never associated,
exported, or referenced explicitly is an unused-extension error: an inert
provider is almost always a forgotten extensions clause rather than intent,
so associate it with its datatype, interface, or type alias, export it, or
reference it with with extensions, a named value.Provider.method() call,
or an explicit ?{Question: Provider} witness. The scope is lexical, value-producing, and is not
re-exported or dynamically inherited by called functions.
For a canonical implementation, prefer associating the provider with its nominal owner (or with a transparent alias view over that owner). A lexical scope is primarily for a consumer-owned alternate policy, especially when the choice affects operators or omitted witnesses and therefore cannot be reduced to one named call.
For one call, name the provider directly after the receiver:
rendered = value.Provider.DebugMethods.render()This selects Provider.DebugMethods without activating it for
value.render() or any surrounding expression. Only members declared with
method may use this form. Extension type arguments are inferred from the
receiver, and declared prerequisites use ordinary extension resolution. A
provider named across a module boundary must be exported. Use its explicit
extension-value form when a call must override prerequisite providers.
The provider and consumer would normally be separate source files in the same package (or in visible packages). This complete two-file example shows the placement and the qualified name:
provider.nl:module Provider
export extension IntCompare : ICompare for Int method compare(left : Int, right : Int) -> Int do if left < right then -1 elsif left > right then 1 else 0 end endend
consumer.nl:module Consumer
func compare_with_zero(value : Int) -> Int do with extensions Provider.IntCompare do value.compare(0) endend
func main() -> Unit do Debug.trace(compare_with_zero(1));endThe lexical scope must name an exported, unassociated provider when crossing a
module boundary. Multiple providers may be listed with commas; repeating a
provider is idempotent. Associated providers, such as an extension listed
after a datatype’s extensions clause, do not need lexical adoption at each
call site. A lexical interface extension can override an associated default
only when its @preferred_over graph covers the complete associated survivor
frontier; contextual witnesses always remain stronger.
@preferred_over(DefaultProvider) uses a bare extension name. The target is
looked up in the current module’s extension namespace and among providers
associated with the source extension’s subject and interface. A provider from
another module may remain private when its public datatype, type alias, or
interface association exposes the provider identity. This does not make that
provider name usable in with extensions, a named call, or an explicit
witness answer. Qualified preference targets such as
@preferred_over(Module.Provider) are not supported yet.
Explicit extension witnesses
Section titled “Explicit extension witnesses”When an API has more than one possible provider, or when the choice is part of the API’s meaning, pass the provider explicitly. A contextual question binds the selected dictionary under an uppercase name:
interface IFormat func format(value : Self) -> Stringend
extension DecimalFormat : IFormat for Int func format(value : Int) -> String do value.to_string() ++ ".00" endend
func render[T]( value : T, ?{extension Format: IFormat for T},) -> String do Format.format(value)end
func main() -> Unit do Debug.trace(render(12, ?{Format: DecimalFormat}));end?{Format: DecimalFormat} is an explicit answer: it bypasses provider
resolution for that question and passes the named extension dictionary to the
function. The witness is a normal immutable value inside the function, so its
ordinary dictionary members are called as Format.format(value). The
contextual question’s extension form, candidate resolution, and associated
providers are covered in Question parameters.
The standard library uses these same rules for arithmetic, equality, ordering, debugging, iteration, indexing, and conversions. When the selected provider must remain part of a value’s static identity, use a witness-indexed type instead of storing the dictionary as an ordinary field.
The standard equality, comparison, hashing, and debugging interfaces also associate providers that the compiler specializes for concrete data shapes. Continue with Structural capabilities to see how those providers recurse through records, arrays, and datatypes.
Next: Witness-indexed types.