Performance
Resid code is written with immutable values, but it does not run like a persistent-data-structure interpreter. The compiler recovers mutation wherever it is safe:
- Tail calls are loops. A recursive call in tail position compiles to a jump.
- Owned collections update in place. When the compiler proves a map,
list or string builder has exactly one owner (its old version is never
read again),
insert,pushand friends write into the existing buffer. This is how aMapbuilt in a loop runs as fast as a hash table. - Builders are linear.
StrBufandListBuf(T)append in amortized O(1), and the type checker guarantees nothing observes them while they grow. - Facts remove checks. Ranges known from literals, loop bounds and conditions let the compiler drop overflow and bounds checks it can prove unnecessary.
- Scalars need no allocation. Integers and floats live in registers; temporary garbage from computing a scalar is freed when the computation ends.
- Known work is gone. Anything computable at compile time is computed at compile time.
Patterns to avoid
Section titled “Patterns to avoid”Growing a string with + in a loop. Each s + piece copies s, so
building an n-piece string that way is O(n²). Use StrBuf, or collect pieces
in a ListBuf(Str) and str_join them once.
Appending to an old version of a list. xs.concat([x]) is cheap when
xs is not used afterwards. Appending to a version you keep using forces
a copy.
Re-reading an accumulator you are building. Pass it along, finish it once.
Merging many maps into one. Build one map through the loop instead of creating a map per step and merging.
The benchmarks compare Resid with other languages on standard programs, with the reasons for each result.
