Language tour
Hello Viv!
Every example below runs in your browser through the same wasm compiler the workbench uses. Running a cell runs it together with every cell above it, starting fresh each time. An edit to one cell changes the results of every cell below it, and nothing is left over from an earlier run.
Numbers
A number is a 64-bit float: 10 means 10.0, and division never
truncates. For counters and indices there is Int, a 64-bit integer declared
with n: Int. print gives the
bare value; show labels a variable with its name and prints any other
expression bare:
Control flow
Three forms: if/else, while, and the counted loop
for i to n, which runs i from 0 to n−1. break and
continue work as in C. Statements end at the newline (no semicolons), and a line
continues automatically inside parentheses or brackets:
Functions
def defines a function, return returns, and a function can call itself. A bare string as
the first statement is a docstring. Parameter and return types (Num,
Arr) are optional annotations, checked when the program compiles.
A 64-bit float (a double)
holds every integer up to 253 exactly, so counting down from 10.0 by
1.0 lands on 1.0, not near it: n <= 1.0 is an
exact test. fact(20.0) is exact as well: 20! is past 253, but it has eighteen
factors of 2, so what is left fits in a double's 53 bits. What a double cannot hold is
a fraction like 0.1. That is where == fails and a tolerance belongs:
0.1 + 0.2 misses 0.3 by 5.6 × 10−17, and
assert_near is how a Viv program says “equal, to within rounding.”
After you run this cell, look under its output. The interpreter records every user-function call, so
each cell carries a collapsible call graph — open here so you can see it.
fact calling itself is the self-loop with its count, and replay animates
the recorded call sequence. In later cells the graph stays folded behind the "call graph" line
until you want it:
Arrays
Arrays hold floats. Literals nest into matrices, indexing takes
comma-separated subscripts (M[i, j], up to rank 4), zeros allocates,
len reads the leading dimension. Arithmetic is elementwise — *
between arrays is the Hadamard product, and an array times a scalar scales. A
show on a matrix indents its rows under the first one, so it reads as a matrix
rather than as a label with a ragged list beneath it:
Plots
A figure is declared in the program. figure(title, xlabel, ylabel) says what the
chart is, and plot(x, y, "label") adds a series to it. Both are emitted as
structured records; whatever runs the program (this page, the workbench, a terminal) draws
them, and the C backend prints the same records as text. Nothing outside the program adds to a figure: the title and
axis labels you see below are the three strings in the cell, and a program that calls
plot without declaring its figure first does not compile. Consecutive
plot calls sharing the same x land in one chart as separate labeled series:
Linear algebra
@ is always the matrix product, never elementwise. Alongside it are
transpose and eig_sym, a Jacobi eigensolver for symmetric matrices
that fills a values vector (ascending) and a vectors matrix (eigenvectors in columns). The
cell checks that the trace equals the sum of the eigenvalues:
Lambdas, quadrature and exact derivatives
fn(x) -> expr is a value: assign it, pass it. The higher-order builtins take
one — simpson and trapezoid integrate, and deriv
differentiates exactly, by forward-mode autodiff at compile time rather than by
finite differences. Its vocabulary is arithmetic plus
exp/sqrt/pow/sin/cos/log/tan; ask it to differentiate through a def you wrote and it stops with an error that says so:
Tests
A test block names a claim and asserts it with assert_near — and
what it prints is the name you wrote: one line per test, however many
assertions are inside. A failure names the test and carries the failing assertion's detail,
and makes the whole run exit nonzero, so a build script can fail on it:
Progress lines
narrate "text" prints a progress line, kept separate from data output. A program can
print "building hamiltonian" or "calculating ERIs" as it reaches each step. Every backend prints it: the interpreter styles the lines, and compiled C prints the
same narrate: text.
Two other kinds of text print nothing. Comments (#) are dropped by the lexer, and
annotation markers (#| what, #|| how, #||| why) are
comments the compiler ignores but viv doc extracts for code browsers:
Parallel loops
parallel for declares a loop parallelizable. The interpreter runs it
sequentially; the C backend emits an OpenMP pragma, so the compiled program spreads it across
cores. On a 12-core laptop the Hartree–Fock calculation on a chain of 20 hydrogen atoms
(hchain_sto3g.viv) goes from 2.67 s on one thread to 0.35 s on twelve:
Records
A record is a named bundle of numbers, arrays and other records. It is built with
every field named, read with a dot, and passed to and returned from a def like any value.
Copying a record copies its numbers, while an array field still refers to the original array,
so bundling a mesh into one value copies no array data. The fields themselves are fixed once
the record is built: to change one, build a new record. A def that returns several values,
return a, b, hands back a record the compiler makes for it, and the caller
unpacks it with lo, hi = extent(v).
One Gaussian for the hydrogen atom
A def with a docstring, deriv and a test, used together
for the first step of a Hartree–Fock calculation: the best single-Gaussian approximation to the
hydrogen atom. The energy comes out −0.4244 hartree, and deriv returns zero at the
analytic optimum, which confirms it is a stationary point of the energy:
Or open the workbench and write something of your own — the full Hartree–Fock, the neutron star and the FEM solver are all in its examples menu.