# Operator Interop Registry `lyng.operators` provides a runtime registry for mixed-class binary operators. Import it when you want expressions such as: - `1 + MyType(...)` - `2 < MyType(...)` - `3 == MyType(...)` to work without modifying the built-in `Int` or `Real` classes. ```lyng import lyng.operators ``` ## Why This Exists If your class defines: ```lyng class Amount(val value: Int) { fun plus(other: Amount) = Amount(value + other.value) } ``` then: ```lyng Amount(1) + Amount(2) ``` works, because the left operand already knows how to add another `Amount`. But: ```lyng 1 + Amount(2) ``` does not naturally work, because `Int` has not been rewritten to know about `Amount`. The operator interop registry solves exactly that problem. ## Mental Model Registration describes a mixed pair: - left class `L` - right class `R` - common class `C` When Lyng sees `L op R`, it: 1. converts `L -> C` 2. converts `R -> C` 3. evaluates the operator as `C op C` So the registry is a bridge, not a separate arithmetic engine. ## API ```lyng OperatorInterop.register( leftClass, rightClass, commonClass, operators, leftToCommon, rightToCommon ) ``` Parameters: - `leftClass`: original left operand class - `rightClass`: original right operand class - `commonClass`: class that will actually execute the operator methods - `operators`: list of operators enabled for this pair - `leftToCommon`: conversion from left operand to common class - `rightToCommon`: conversion from right operand to common class ## Supported Operators `BinaryOperator` values: - `Plus` - `Minus` - `Mul` - `Div` - `Mod` - `Compare` - `Equals` Meaning: - `Compare` enables `<`, `<=`, `>`, `>=`, and `<=>` - `Equals` enables `==` and `!=` ## Minimal Working Example ```lyng package test.decimalbox import lyng.operators class DecimalBox(val value: Int) { fun plus(other: DecimalBox) = DecimalBox(value + other.value) fun minus(other: DecimalBox) = DecimalBox(value - other.value) fun mul(other: DecimalBox) = DecimalBox(value * other.value) fun div(other: DecimalBox) = DecimalBox(value / other.value) fun mod(other: DecimalBox) = DecimalBox(value % other.value) fun compareTo(other: DecimalBox) = value <=> other.value } OperatorInterop.register( Int, DecimalBox, DecimalBox, [ BinaryOperator.Plus, BinaryOperator.Minus, BinaryOperator.Mul, BinaryOperator.Div, BinaryOperator.Mod, BinaryOperator.Compare, BinaryOperator.Equals ], { x: Int -> DecimalBox(x) }, { x: DecimalBox -> x } ) ``` Then: ```lyng import test.decimalbox assertEquals(DecimalBox(3), 1 + DecimalBox(2)) assertEquals(DecimalBox(1), 3 - DecimalBox(2)) assertEquals(DecimalBox(8), 4 * DecimalBox(2)) assertEquals(DecimalBox(4), 8 / DecimalBox(2)) assertEquals(DecimalBox(1), 7 % DecimalBox(2)) assert(1 < DecimalBox(2)) assert(2 <= DecimalBox(2)) assert(3 > DecimalBox(2)) assert(2 == DecimalBox(2)) assert(2 != DecimalBox(3)) ``` ## How Decimal Uses It `lyng.decimal` uses this same mechanism so that: ```lyng import lyng.decimal 1 + 2.d 0.5 + 1.d 2 == 2.d 3 > 2.d ``` work naturally even though `Int` and `Real` themselves were not edited to know `Decimal`. The shape is: - `leftClass = Int` or `Real` - `rightClass = Decimal` - `commonClass = Decimal` - convert built-ins into `Decimal` - leave `Decimal` values unchanged ## Step-By-Step Pattern For Your Own Type ### 1. Pick the common class Choose one class that will be the actual arithmetic domain. For numeric-like types, that is usually your own class: ```lyng class Rational(...) ``` ### 2. Implement operators on that class The common class should define the operations you plan to register. Example: ```lyng class Rational(val num: Int, val den: Int) { fun plus(other: Rational) = Rational(num * other.den + other.num * den, den * other.den) fun minus(other: Rational) = Rational(num * other.den - other.num * den, den * other.den) fun mul(other: Rational) = Rational(num * other.num, den * other.den) fun div(other: Rational) = Rational(num * other.den, den * other.num) fun compareTo(other: Rational) = (num * other.den) <=> (other.num * den) static fun fromInt(value: Int) = Rational(value, 1) } ``` ### 3. Register the mixed pair ```lyng import lyng.operators OperatorInterop.register( Int, Rational, Rational, [ BinaryOperator.Plus, BinaryOperator.Minus, BinaryOperator.Mul, BinaryOperator.Div, BinaryOperator.Compare, BinaryOperator.Equals ], { x: Int -> Rational.fromInt(x) }, { x: Rational -> x } ) ``` ### 4. Use it ```lyng assertEquals(Rational(3, 2), 1 + Rational(1, 2)) assert(Rational(3, 2) == Rational(3, 2)) assert(2 > Rational(3, 2)) ``` ## Registering More Than One Built-in Type If you want both `Int + MyType` and `Real + MyType`, register both pairs explicitly: ```lyng OperatorInterop.register( Int, MyType, MyType, [BinaryOperator.Plus, BinaryOperator.Compare, BinaryOperator.Equals], { x: Int -> MyType.fromInt(x) }, { x: MyType -> x } ) OperatorInterop.register( Real, MyType, MyType, [BinaryOperator.Plus, BinaryOperator.Compare, BinaryOperator.Equals], { x: Real -> MyType.fromReal(x) }, { x: MyType -> x } ) ``` Each mixed pair is independent. ## Pure Lyng Registration This mechanism is intentionally useful from pure Lyng code, not only from Kotlin-backed modules. That means you can: - declare a class in Lyng - define its operators in Lyng - register mixed operand bridges in Lyng without touching compiler internals. ## Where To Register Register once during module initialization. Top-level module code is a good place: ```lyng package my.rational import lyng.operators class Rational(...) OperatorInterop.register(...) ``` That keeps registration close to the type declaration and makes importing the module enough to activate the interop. ## What Registration Does Not Do The registry does not: - invent operators your common class does not implement - change the original `Int`, `Real`, or other built-ins - automatically cover every class pair - replace normal method overload resolution when the left-hand class already knows what to do It only teaches Lyng how to bridge a specific mixed pair into a common class for the listed operators. ## Recommended Design Rules If you want interop to feel natural: - choose one obvious common class - make conversions explicit and unsurprising - implement `compareTo` if you want ordering operators - register `Equals` whenever mixed equality should work - keep the registered operator list minimal and accurate For decimal-like semantics, also read [Decimal.md](Decimal.md).