Gotchas
Every item on this page was found the same way: code that ran, produced numbers, and was wrong, with no error and output that looked like output.
An array language compresses a lot of behaviour into very little text, so a one-character mistake is a whole different program rather than a syntax error. Vigilance is no defence. What works is a differential test: run anything numerically important against a second implementation and compare exactly. Several of these were caught that way and no other.
A comment line of just / used to swallow the rest of the file without a word: it opens a block
comment that a line of just \ closes, and since 2.0.0 one left open is a parse error. Dotted names such as
bt.idx:{…} now survive \l, and a / note at the end of a line inside a multi-line
lambda comments out that line only. A lambda with too many locals fails with a limit error
(error[E0108]) instead of a vague one; split it into smaller ones.
1. til #x parses as a train, not a count
til followed by # does not mean "the first n integers where
n is the count of x". It parses as a composition and gives you something else entirely,
without complaint. Write !#x.
2. A fully-applied projection under each evaluates once
This is the worst one on the page, because the output looks completely normal.
{[U] 3#U?U}[U]'til NB / WRONG: returns the SAME basket NB times
{[i;U] 3#U?U}[;U]'til NB / RIGHT: leave the iterated slot openIn the first form every argument is already supplied, so the expression is evaluated once and the result is repeated. You get a list of the right length, of the right type, containing the right kind of value, all identical. In a randomised sweep that produced a 13× flattering benchmark before anyone noticed the variance was zero.
After any sampled or randomised each, run #?r for the count of distinct
results. If it is 1 and you expected n, you have this bug.
3. An out-of-range gather returns nulls, silently
Indexing a vector past its end yields nulls where NumPy raises IndexError. On floats
the 0ns then spread through every downstream scan and reduction. On ints it's quieter: since
2.9.0 an int null stays null in arithmetic, as q's does, but sums skip it, so +/(1 2 3)@4 5 is just
0. Either way the run completes with output that looks like output.
This surfaced when a 3,600-day start grid plus 756-day observation offsets overran a 2,600-day price history: the entire tensor filled with nulls and every summary statistic came back plausible. Assert your bounds explicitly before the gather:
if[T <= (|/S) + |/off; '"price history too short"];4. -0.0 and 0.0: one value (and now they sort that way too)
Since 2.2, -0.0 and 0.0 are one value everywhere it
matters: =, ~, in, = (group), ?
(distinct) and find all treat them as the same number, as q does. Before 2.2 ? and
find disagreed with the rest, and worse, which answer you got depended on the
vector’s length, because the short and long generic paths used different comparisons:
#?(-0.0 0.0) / 2 <- before 2.2
#?(300#-0.0 0.0) / 1 <- the same two values, six hundred times
#?(-0.0 0.0) / 1 <- 2.2, at every lengthUntil 2.4 the collation still put -0.0 before 0.0.
Since 2.4.0 it doesn’t: the two sort as one value and grades keep them in place, so
<(0.0;-0.0) is 0 1, as in q. Every NaN is one value too, sorting first.
The keys-only sort (asc, x@<x) never changes an item, so equal zeros
can come out in bit order, which is still ascending by value. Still, don’t write code that
depends on where a signed zero lands.
5. 2.9.0 changed a few meanings, to q's
Code written for 2.8 can read differently now. The ones that bite: find of a missing item is the count
(1 2 3?4 is 3, it was 0N), 0N+1 is 0N, -8! and
-9! are q's bytes (Amber's own are `ser and `des), 09:30:00 is a second
(a time keeps its ms: 09:30:00.000), and a missing item of a list of lists is its empty list, not nulls.
The changelog
has the full list.
Habits that catch all of them
- Differential-test anything numerical. A second implementation in a language with different failure modes (NumPy is ideal, because it raises where Amber returns nulls) catches items 6, 7 and 8 immediately and items 1–5 by their consequences.
- Check variance, not just shape.
#?ron any randomised result. - Check for nulls after every gather that uses computed indices:
+/^rshould be 0. - Load your scripts in a fresh process and list what got defined. Items 1, 2 and 5 all manifest as "the definition is just not there".