# Operators as methods We need a convention to allow customizing operators for user defined types. Mapping them to method invocations has been widely supported. This document: - surveys some existing languages - recaps desugaring - raises the question of null-application - recaps overloading so that BigInt / Int32 and BigInt / BigInt could work - captures some existing styles in a table to provoke discussion [Example of trying out Java BigInteger on different styles.](https://hackmd.io/n63XbmRZRv-jA63KKH9qcw) ## Existing languages: | PL | Style | Example | | ---- | ---- | ---- | | [F#](https://learn.microsoft.com/en-us/dotnet/fsharp/language-reference/operator-overloading#overloaded-operator-names) | Prefix, descriptive | op_Addition | [Python](https://docs.python.org/3/reference/datamodel.html#specialnames) | Wunderbars for special (aka magic) methods, imperative style | a.\_\_add\_\_(b) | | [Kotlin](https://kotlinlang.org/docs/operator-overloading.html#arithmetic-operators) | `operator` keyword + known names with non-infix prefixes | .plus(...) / .unaryMinus(...) | | [C++](https://en.cppreference.com/w/cpp/language/operators.html) | keyword and punctuation | `operator+` | | Scala | unified style based on arity and non-identifier method names | `a * b` is `a.*(b)` | ## Sugar: `a != b` is syntactic sugar for `!(a == b)` Compound assignments are sugar. `a += b` captures `a` and turns it into an assignment with a `+` operator Postfix `++` and `--` are syntactic sugar for applications of the prefix version. Comparisons like `a > b` and `a <= b` could be syntactic sugar for uses of the `<` operator and or a negation of thereof, or syntactic sugar for uses of general comparison: `(a <=> b) < 0` # Null splitting Some operations seem like they should apply to nullable types: - stringification / debug-value-ification - equality - comparison Do we just special case those? Or do we have a general mechanism for applying operators to nullable types? ```ts let a: A? = ...; a 𑑛 b // -> if (isNull(a)) { (null as Never<A>?) 𑑛 b } else { a 𑑛 b } // -> if (isNull(a)) { (null as Never<A>?).squiggle(b) } else { a.squiggle(b) } ``` By splitting out the null case, we could auto-employ extension methods ```ts @extension("toString") let nullToString<T extends AnyValue>( x: Never<T>? ): String { "null" } ``` ## Overloading If we want to allow mixed operands, we need some way to allow overloading, at least for operators. But overloading only tends to translate well to static languages, and there it's problematic. Each language has it's own definition of what constitutes unambiguous overloading. E.g. Java's distinct signatures depend on distinctness w.r.t. *raw types* / JVM signatures. Use-site overloading: ```ts subject.method_(args) ``` The `_` there could *etc.* and allow picking an appropriate overload that starts with the segment list \["method"]. If subject's type were defined as below, then we might rewrite a `.method_` application to *methodWithFoo* or *methodWithBar* based on available type info. ```ts interface SubjectType { methodWithFoo(foo: Foo): ...; methodWithBar(bar: Bar): ...; } ``` ### Segmenting hazards We currently have snake-chimera style: an identifier style meant to allow segmenting identifiers: > Every segment is either at the start, preceded by `_`, or starts with a capital letter - Prefer `XmlHttpRequst` to `XMLHTTPRequest` since the segment boundary between Xml and Http is clear - Use `_` for segmenting in unicameral writing systems like CJK - Treat `bzip2` as one segment so `deg2rad` (degrees to radians from numpy) would need to be `deg_2_rad` in Temper and `2to3` (Python 2 to Python3 migration tool) would need to be `_2To_3` or `_2_to_3` Encouraging sementable identifiers is good for translatability and also good for this overloading scheme. ## Table of possible method styles Here's a table of operators with some styles for operator naming. I tried some styles: - short, non-imperative names that don't imply mutation and which do not explicitly mention the operator type - a variant, qualified 1 style, where non-infix operators are prefixed - a variant, qualified 2 style, where all operators are prefixed - an imperative style: *add* instead of *plus* Not shown: - F#'s long style - Python's wunderbar "style" The prefixes might avoid confusion | Op | Short | Qualify1 | Qualify2 | Verbish | Nym-ish | | ---------------- | ------------------- | -------------- | ---------------- | ------------------- | ---- | | infix `+` | plus | " | infixPlus | add | `infix"+"` | | prefix `+` | numerify | prefixNumerify | " | numerify | `prefix"+"` | | infix `-` | minus | " | infixMinus | subtract | `infix"-"` | | prefix `-` | negation | prefixNegation | negate | | `prefix"-"` | | `*` | times | " | infixTimes | multiply | `infix"*"` | | `/` | div (quotient?) | " | infixDiv | divide | `infix"/"` | | `**` | pow | " | infixPow | ? | `infix"**"` | | `%` | mod (? or rem) | " | infixMod | ? | `infix"%"` | | `&` | and (intersection?) | " | infixAnd | intersect | `infix"&"` | | `\|` | or (union?) | " | infixOr | union | `infix"\|"` | | `^` | xor | " | infixXor | ? | `infix"^"` | | prefix `~` | inverse | prefixInverse | " | invert | `prefix"~"` | | prefix `++` | next | prefixNext | " | increment | `prefix"++"` | | prefix `--` | previous | prefixPrevious | " | decrement | `prefix"--"` | | `<<` | leftShift | " | infixLeftShift | shiftLeft | `infix"<<"` | | `>>` | rightShift | " | infixRightShift | shiftRight | `infix">>"` | | `>>>` | uRightShift | " | infixURightShift | uShiftRight | `infix">>>"` | | `==` | equals | " | infixEquals | ? | `infix"=="` | | `<=>` | difference | " | infixDelta | compare / compareTo | `infix"<=>"` | | `in` | contains | " | | | `infix"in"` | | `...[...]` | get | " | bracketGet | | `postfix"[]"` | | `...[...] = ...` | set | " | bracketSet | | `assign"[]"` | | stringify | toString | | debuggify | toDebug | Shaw: I don't like Qualify1 or Qualify2, Short and nouning a verb feel like good ideas. Verbish feels bad when writing a bitwise OR having to say intersect. Maybe a # Shaw's notes ## Do we want casting operators Our existing toString is much like an operator thtat deals with casting. ```ts class Name(text: String) { public toString() { return "Name(${text})"; } } ``` ## NYM-alike nym`a+b` might be doable, maybe fixing the names to `a` and `b` letting us do more unary like `+a`. ```ts class Spam(private value: Int) { public nym`a+b`(other: Int): Int { return value + other; } public nym`+a`(): Int { return value; } } ``` # unary`op` and binary`op` ```ts class Eggs(private value: Int) { public infix`+`(other: Int): Int { return value + other; } public postfix`+`(): Int { return value; } } ``` Maybe even a specialized nym that handles this early, and do we allow for Void returns: ```ts class Oppenmülleimer { public op`+a`(): Void {} } ``` ## Dlang D has `opBinary` that roughly equates to Temper's: ```ts let opBinary<op: String>(): Int if (op == "+") { return this.value + other.value; } ``` ## \_\_getattr\_\_ / \_\_setattr\_\_ Do we want an equivalent to python's getattr and setattr. I could imagine it being useful `f.x` where `f` does not have a property or method `x`, it calls `f.getAttr("x")` or however `getAttr` would be named ## \_\_call\_\_ Call overloading is a thing that happens, C++ especially. ## Some thoughts from Tom My feelings: - Be as simple as possible, best bang for buck - If it's written like you say it, where not overly long, that's easier to learn. "a plus b" when reading suggests `a.plus(b)` as a desugaring for `a + b` - We typically lean kotlin when not js - What's best for temper isn't always what's best for translation, so keep that in mind somewhat - More automatic the better, when not confusing nor overly limiting (not surprising, I mean, mostly)