Thinking in arrays: the one idea everything else rests on
Amber is not a language with array features. It is a language where the array is the only unit of thought. Every habit you bring from Python, C or Java will fight you for about an hour, and then stop.
This chapter is the hour. None of it is syntax you need to memorise. It is the three ideas that make the rest of the documentation readable.
Idea 1 — the loop is already written
In most languages you describe how to visit each element. In Amber you name the operation and the whole array is the operand:
x: 1 2 3 4 5
x * 2 / 2 4 6 8 10 -- no loop, no map, no comprehension
x + x / 2 4 6 8 10
+/ x / 15 -- sum: the verb + folded over x
x > 2 / 0 0 1 1 1 -- comparison is elementwise tooThat last line matters more than it looks. A comparison does not give you a bool; it gives you a mask, one byte per element, and masks are how you select data:
x @ & x > 2 / 3 4 5 -- & turns a mask into indices, @ takes them
+/ x @ & x > 2 / 12 -- sum of the elements above 2+/x@&x>2 looks like four passes over the data. The compiler recognises the shape and emits one fused primitive that never materialises the mask or the index vector. Writing the idiomatic thing and writing the fast thing are the same act, which is what the design is for.
Idea 2 — right to left, always, no exceptions
Amber has no operator precedence. Every expression is evaluated right to left. This is the single most common source of early confusion, and it takes ten minutes to internalise:
2 * 3 + 4 / 14, not 10 -- it reads as 2 * (3 + 4)
(2 * 3) + 4 / 10 -- parenthesise when you mean itThe rule is uniform, which is what makes it worth having. There is no table of sixteen precedence levels to remember, and no expression whose meaning depends on knowing whether % binds tighter than <<.
A monadic (one-argument) verb takes everything to its right:
# 1 2 3 / 3 -- # is `count` here
| 1 2 3 / 3 2 1 -- | is `reverse` here
# | 1 2 3 / 3 -- count of the reverse of 1 2 3Idea 3 — one glyph, two verbs
Most symbols are ambivalent: they mean one thing with one argument and another with two. This is not overloading for cleverness. The two meanings are almost always the same idea at different arity:
| glyph | monadic (one arg) | dyadic (two args) |
|---|---|---|
# | count | take / reshape |
| | reverse | max / or |
& | where (mask → indices) | min / and |
? | distinct | find |
! | enumerate (!5) | dict / mod |
$ | string | cast / pad |
You never have to guess which one you got: the count of arguments decides it, and \h at the prompt prints the whole table on one screen.
Putting the three together
Here is a real expression. Read it right to left and it decomposes cleanly:
+/ (x * x) @ & x > 0
/ 4. sum them
/ 3. take those positions from x*x
/ 2. positions where the mask is 1
/ 1. mask of x above zeroFour ideas, one line, one pass over memory. If you can read that, you can read Amber.
What to do next
Do not read on yet. Open a REPL and type the examples above until the right-to-left rule stops surprising you. The next chapter gets you that REPL.