Update operators docs to include various undocumented features like
reducers and min/max and ++ and <>
This commit is contained in:
parent
c97ed75dff
commit
27eff711cd
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user