tomo/docs/reductions.md
2024-10-08 23:39:37 -04:00

2.5 KiB

Reductions

In Tomo, reductions are a way to express the idea of folding or reducing a collection of values down to a single value. Reductions use an infix operator surrounded by parentheses, followed by a collection:

nums := [10, 20, 30]
sum := (+) nums
>> sum
= 60

Reductions can be used as an alternative to generic functions like sum(), product(), any(), and all() in Python, or higher-order functions like foldl and foldr in functional programming:

# Sum:
>> (+) [10, 20, 30]
= 60

# Product:
>> (*) [2, 3, 4]
= 24

# Any:
>> (or) [no, yes, no]
= yes

# All:
>> (and) [no, yes, no]
= no

Minimum and Maximum

Reductions are especially useful for finding the minimum or maximum values in a collection using the _min_ and _max_ infix operators.

# Get the maximum value:
>> (_max_) [10, 30, 20]
= 30

# Get the minimum value:
>> (_min_) [10, 30, 20]
= 10

The _min_ and _max_ operators also support field and method call suffixes, which makes it very easy to compute the argmin/argmax (or keyed minimum/maximum) of a collection. This is when you want to get the minimum or maximum value according to some feature.

# Get the longest text:
>> (_max_.length) ["z", "aaaaa", "mmm"]
= "aaaaa"

# Get the number with the biggest absolute value:
>> (_max_:abs()) [1, -2, 3, -4]
= -4

Comprehensions

Reductions work not only with iterable values (arrays, sets, integers, etc.), but also with comprehensions. You can use comprehensions to perform reductions while filtering out values or while applying a transformation:

# Sum the lengths of these texts:
>> (+) t.length for t in ["a", "bc", "def"]
= 6

# Sum the primes between 1-100:
>> (+) i for i in 100 if i:is_prime()
= 1060

Empty Collection Behavior

If a collection has no members, the default behavior for a reduction is to create a runtime error and halt the program with an informative error message. If you instead want to provide a default fallback value, you can use else: to give one:

empty := [:Int]
>> (+) empty else: -1
= -1

>> (+) empty
# Error: empty iterable!

You can also provide your own call to fail() or exit() with a custom error message, or a short-circuiting control flow statement (return, stop, skip) like this:

>> (_max_) things else: exit("No things!")

for nums in num_arrays:
    product := (*) nums else: skip
    do_thing(product)

func remove_best(things:[Thing]):
    best := (_max_.score) things else: return
    best:remove()