Update operators docs to include various undocumented features like

reducers and min/max and ++ and <>
This commit is contained in:
Bruce Hill 2024-08-19 20:15:55 -04:00
parent c97ed75dff
commit 27eff711cd

View File

@ -1,4 +1,186 @@
# Operator Overloading
# Operators
Tomo supports a number of operators, both infix and prefix:
- `+`,`-`,`*`,`/`: addition, subtraction, multiplication, and division for
integers and floating point numbers
- `^`: exponentiation for integers and floating point numbers
- `mod`: modulus for integers and floating point numbers
- `mod1`: clock-style modulus, which is equivalent to `1 + ((x-1) mod y)`. This
is particularly useful for doing wraparound behavior on 1-indexed arrays.
- `++`: concatenation (for text and arrays)
- `<<`, `>>`: bitwise left shift and right shift for integers
- `_min_`/`_max_`: minimum and maximum (see below)
- `<`, `<=`, `>`, `>=`, `==`, `!=`: comparisons
- `<>`: the signed comparison operator (see below)
- `and`, `or`, `xor`: logical operations for `Bool`s and bitwise operations for
integers
## Signed Comparison Operator
When performing comparisons, Tomo will internally use APIs that take two
objects and return a signed 32-bit integer representing the relationship
between the two objects. The `<>` operator exposes this signed comparison
value to the user.
```tomo
>> 0 <> 99
= -1_i32
>> 5 <> 5
= 0_i32
>> 99 <> 0
= 1_i32
```
It's particularly handy for using the array `sort()` method, which takes a
function that returns a signed integer:
```tomo
>> foos:sort(func(a,b:&Foo): a.length <> b.length)
```
## Reducers
A novel feature introduced by Tomo is "reducer" operators, which function
similar to the `reduce` function in functional programming, but implemented
as a core language feature that runs as an inline loop. Reducers eliminate
the need for several polymorphic functions used in other languages like
`sum`, `any`, `all`, `reduce`, and `fold`. Here are some simple examples of
reducers in action:
```tomo
>> nums := [10, 20, 30]
>> (+) nums
= 60
>> (or) n > 15 for n in nums
= yes
>> texts := ["one", "two", "three"]
>> (++) texts
= "onetwothree"
```
The simplest form of a reducer is an infix operator surrounded by parentheses,
followed by either something that can be iterated over or a comprehension.
Results are produced by taking the first item in the iteration and repeatedly
applying the infix operator on subsequent items in the iteration to get a
result. Specifically for `and` and `or`, the operations are short-circuiting,
so the iteration will terminate early when possible.
## Handling Empty Iterables
In the case of an empty iteration cycle, there are two options available. The
first option is to not account for it, in which case you'll get a runtime error
if you use a reducer on something that has no values:
```tomo
>> nums := [:Int]
>> (+) nums
Error: this collection was empty!
```
If you want to handle this case, you can either wrap it in a conditional
statement or you can provide a fallback option with `else` like this:
```tomo
>> (+) nums else: 0
= 0
```
The `else` clause must be a value of the same type that would be returned.
## Extra Features
As syntactic sugar, reducers can also access fields, indices, or method calls.
This simplifies things if you want to do a reduction without writing a full
comprehension:
```tomo
struct Foo(x,y:Int):
func is_even(f:Foo)->Bool:
return (f.x + f.y) mod 2 == 0
>> foos := [Foo(1, 2), Foo(-10, 20)]
>> (+).x foos
= -9
// Shorthand for:
>> (+) f.x for f in foos
= -9
>> (or):is_even() foos
= yes
// Shorthand for:
>> (or) f:is_even() for f in foos
>> (+).x:abs() foos
= 11
```
## `_min_` and `_max_`
Tomo introduces a new pair of operators that may be unfamiliar: `_min_` and
`_max_`. The `_min_` and `_max_` operators are infix operators that return the
larger or smaller of two elements:
```tomo
>> 3 _max_ 5
= 5
>> "XYZ" _min_ "ABC"
= "ABC"
```
Initially, this might seem like a fairly useless operator, but there are two
tricks that make these operators extremely versatile.
### Keyed Comparisons
The first trick is that the comparison operation supports keyed comparisons
that operate on a field, index, method call, or function call. This lets you
choose the larger or smaller of two elements _according to any standard_.
Here's some examples:
```tomo
// Get the largest absolute value number:
>> 3 _max_:abs() -15
= -15
struct Person(name:Text, age:Int)
// Get the oldest of two people:
>> Person("Alice", 33) _max_.age Person("Bob", 20)
= Person(name="Alice", age=33)
// Get the longest of two arrays:
>> [10, 20, 30, 40] _max_.length [99, 1]
= [10, 20, 30, 40]
// Get the array with the highest value in the last position:
>> [10, 20, 999] _max_[-1] [99, 1]
= [10, 20, 999]
```
The keyed comparison can chain together multiple field accesses, array index
operations, method calls, etc. If you wanted to, for example, get the item
whose `x` field has the highest absolute value, you could use `_max_.x:abs()`.
### Working with Reducers
The second trick is that the `_min_` and `_max_` operators work with reducers.
This means that you get get the minimum or maximum element from an iterable
object using them:
```tomo
>> nums := [10, -20, 30, -40]
>> (_max_) nums
= 30
>> (_max_:abs()) nums
= -40
```
## Operator Overloading
Operator overloading is supported, but _strongly discouraged_. Operator
overloading should only be used for types that represent mathematical concepts
@ -19,7 +201,7 @@ Bad candidates would include:
- Objects that represent regular expressions
- Objects that represent filesystem paths
## Available Operator Overloads
### Available Operator Overloads
When performing a math operation between any two types that are not both
numerical or boolean, the compiler will look up the appropriate method and
@ -31,7 +213,7 @@ depending on the specifications of the specific operator.
If no suitable method is found with the appropriate types, a compiler error
will be raised.
### Addition
#### Addition
```
func plus(T, T)->T
@ -40,7 +222,7 @@ func plus(T, T)->T
In an addition expression `a + b` between two objects of the same type, the
method `a:plus(b)` will be invoked, which returns a new value of the same type.
### Subtraction
#### Subtraction
```
func minus(T, T)->T
@ -49,7 +231,7 @@ func minus(T, T)->T
In a subtraction expression `a - b` between two objects of the same type, the
method `a:minus(b)` will be invoked, which returns a new value of the same type.
### Multiplication
#### Multiplication
```
func times(T, T)->T
@ -61,7 +243,7 @@ if `a` and `b` are the same non-numeric type, or `a:scaled_by(b)` if `a` is
non-numeric and `b` is numeric, or `b:scaled_by(a)` if `b` is non-numeric and
`a` is numeric. In all cases, a new value of the non-numeric type is returned.
### Division
#### Division
```
func divided_by(T, N)->T
@ -70,7 +252,7 @@ func divided_by(T, N)->T
In a division expression `a / b` the method `a:divided_by(b)` will be invoked
if `a` has type `T` and `b` has a numeric type `N`.
### Exponentiation
#### Exponentiation
```
func power(T, N)->T
@ -79,7 +261,7 @@ func power(T, N)->T
In an exponentiation expression, `a ^ b`, if `a` has type `T` and `b` has a
numeric type `N`, then the method `a:power(b)` will be invoked.
### Modulus
#### Modulus
```
func mod(T, N)->T
@ -89,7 +271,7 @@ func mod1(T, N)->T
In a modulus expression, `a mod b` or `a mod1 b`, if `a` has type `T` and `b`
has a numeric type `N`, then the method `mod()` or `mod1()` will be invoked.
### Negative
#### Negative
```
func negative(T)->T
@ -98,7 +280,7 @@ func negative(T)->T
In a unary negative expression `-x`, the method `negative()` will be invoked
and will return a value of the same type.
### Bit Operations
#### Bit Operations
```
func left_shift(T, Int)->T
@ -116,7 +298,7 @@ In a bitwise binary operation `a and b`, `a or b`, or `a xor b`, then the
method `bit_and()`, `bit_or()`, or `bit_xor()` will be invoked, assuming that
`a` and `b` have the same type, `T`. A value of type `T` will be returned.
### Bitwise Negation
#### Bitwise Negation
```
func negated(T)->T