Operator overloading
Noodle overloads existing operators through standard Prelude interfaces. A type supplies an extension provider for the corresponding interface; the language does not allow declarations of new operator symbols or changes to precedence and associativity.
Primitive Int and Double operations use built-in semantics. Providers are
used when an overloadable operator has a non-primitive receiver.
Associate a provider with a type
Section titled “Associate a provider with a type”This Score datatype associates an IAdd[Score] provider. The + expression
then resolves to that provider’s add method:
datatype Score extensions ScoreAdd Score(Int)end
extension ScoreAdd : IAdd[Score] for Score type Output = Score
method add(left : Score, right : Score) -> Score do Score::Score(left._0 + right._0) endend
func combine(left : Score, right : Score) -> Score do left + rightendThe datatype’s extensions clause makes ScoreAdd an associated provider, so
callers do not need to activate or pass it explicitly. Providers can also come
from locally enabled extensions or contextual extension questions; the usual
interface and extension resolution
rules still apply.
Operator interface mapping
Section titled “Operator interface mapping”An overloadable operator uses the same provider and method lookup as the corresponding method form:
| Operator | Prelude interface | Method form |
|---|---|---|
-x |
INegate |
x.negate() |
x + y |
IAdd[Rhs] |
x.add(y) |
x - y |
ISubtract[Rhs] |
x.subtract(y) |
x * y |
IMultiply[Rhs] |
x.multiply(y) |
x / y |
IDivide[Rhs] |
x.divide(y) |
x % y |
IRemainder[Rhs] |
x.remainder(y) |
x ++ y |
IConcat[Rhs] |
x.concat(y) |
x == y |
IEqual |
x.equal(y) |
x != y |
IEqual |
!(x.equal(y)) |
x < y |
ICompare |
x.compare(y) < 0 |
x <= y |
ICompare |
x.compare(y) <= 0 |
x > y |
ICompare |
x.compare(y) > 0 |
x >= y |
ICompare |
x.compare(y) >= 0 |
x <=> y |
ICompare |
Ordering view of Int applied to x.compare(y) |
IEqual and ICompare are homogeneous: the right operand has the receiver’s
type. equal returns Bool, while compare returns a negative integer, zero,
or a positive integer. The three-way operator <=> classifies that integer
sign into the standard Ordering datatype (Less, Equal, or Greater),
which can be pattern matched directly. Many structural values can use
compiler-generated providers; see Structural capabilities.
++ on a String receiver keeps the built-in concatenation; other receivers
resolve IConcat[Rhs]. The standard library associates
IConcat[String] for String with that built-in concatenation, so generic code
can constrain concatenation uniformly.
switch left <=> rightcase Ordering::Less then "before"case Ordering::Equal then "same"case Ordering::Greater then "after"endOrdering::Less, Ordering::Equal, and Ordering::Greater are also view
patterns over Int, so an integer sign can be classified directly:
switch deltacase Ordering::Less then "decreased"case Ordering::Equal then "unchanged"case Ordering::Greater then "increased"endChecked indexing, source[index]!, follows the related built-in
IIndexGet[Index] for Source protocol. It selects an associated Element type
and propagates IndexError, but it is not an arithmetic operator interface.
Use a different right-hand type
Section titled “Use a different right-hand type”Arithmetic interfaces take an explicit Rhs type and define an associated
Output type. Neither has to equal the receiver type:
datatype Vec2 extensions Vec2Scale Vec2{x: Double, y: Double}end
extension Vec2Scale : IMultiply[Double] for Vec2 type Output = Vec2
method multiply(vector : Vec2, factor : Double) -> Vec2 do Vec2::Vec2{x: vector.x * factor, y: vector.y * factor} endend
func scale(vector : Vec2, factor : Double) -> Vec2 do vector * factorendThis provider defines Vec2 * Double -> Vec2. It does not define
Double * Vec2, Vec2 * Vec2, or any other operator pairing. Each pairing
needs its own applicable provider.
State operator requirements in generic code
Section titled “State operator requirements in generic code”Generic code requests the exact capability it uses. Constraining Output
keeps the function’s result type equal to T:
func add_same[T]( left : T, right : T, ?{extension Add: IAdd[T, Output = T] for T},) -> T do left + rightendOperator resolution matches the receiver against Self and the right operand
against Rhs. When Rhs is not otherwise known, resolution first tries the
homogeneous pairing Rhs = Self. No applicable provider produces an
undefined-operator diagnostic; multiple applicable providers produce an
ambiguous-operator diagnostic. An expected result type does not choose among
otherwise ambiguous providers.
The associated methods remain available as ordinary dot calls, such as
left.add(right) and left.equal(right). Use a named contextual provider such
as Add.add(left, right) when the provider choice itself should be explicit.
Operators that cannot be overloaded
Section titled “Operators that cannot be overloaded”The following forms keep fixed language semantics:
- logical
!,&&, and||; - unary
+; - integer bitwise
~~~,&&&,^^^,|||,<<<,>>, and>>>; - string concatenation
++; - option fallback
??.
An overloaded receiver is evaluated exactly once. Ordinary binary operands are evaluated left to right before the selected method runs, matching the corresponding method call’s evaluation behavior.
Next: Open types.