aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBruce Hill <bruce@bruce-hill.com>2024-08-19 20:15:55 -0400
committerBruce Hill <bruce@bruce-hill.com>2024-08-19 20:15:55 -0400
commit27eff711cd66fa8a96f2bbf43460aa6f875da101 (patch)
tree45068fe7b7e03160e7a9b88cd91bd5c65bc3044d
parentc97ed75dfffaa26a91dc029a95dffa94ff5c4190 (diff)
Update operators docs to include various undocumented features like
reducers and min/max and ++ and <>
-rw-r--r--docs/operators.md204
1 files changed, 193 insertions, 11 deletions
diff --git a/docs/operators.md b/docs/operators.md
index 422c097b..a3c9e01e 100644
--- a/docs/operators.md
+++ b/docs/operators.md
@@ -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