Spreadsheets are everywhere: in businesses, labs, universities, and homes. But they’re also notoriously error-prone. Studies show that many spreadsheets contain errors. Errors that sometimes go unnoticed until it's too late. That is partly because spreadsheets, while immensely popular, lack robust ways to catch errors before they matter.
At the same time, programming spreadsheets is awkward. Anything that goes beyond basic data science may quickly become a VBA hack or the like. Major spreadsheet formula languages grew organically. Some spreadsheet software caught up with the need for higher-level abstractions and implement LAMBDA. But it has become apparent that there is no clear way for writing high-level formulas that interacts with the outside world.
recalc
tackles these problems by embedding a novel formula language into
spreadsheets. The proposed formulas allow for:
In recalc, writing formulas feels familiar to spreadsheet users: cell references
are written in the usual form A1
, and cell ranges separated by a colon (for
example A1:B3
).
Like any spreadsheet, you work with literal values. All of the following are available:
-1
, 0
, 1.2
, etc. — numbersFALSE
, TRUE
— booleans"Hello, World!"
— strings (with support for value interpolation via ${e}
inside strings)Formulas (written as e
, e'
) may refer to defined functions (f
) or bound variables (x
):
A1
— a simple reference to cell A1A1:B3
— a reference to a block (which “spills” across the shape)\x -> e
— a lambda (anonymous) function that takes x
and computes e
f(e, e', ...)
— function callslet x: t = e in e'
— a let-binding with type annotation t
{l: e}
— records (like small labeled dictionaries)e.l
— to project (access) a field l
from record e
e: t
— type annotationEvents are syntactically distinct from regular expressions and prefixed with
~
. For example, the formula @Temparature
may refer to a discrete event
source with temperature values.
Types are checked and inferred. Syntactic restrictions make sure that types remain inferrable and make sure that there is no need for type annotations.
Implicit types (written as {t:Type}
if its kind is of Type
we may write
{t}
) are inferred by a unification and resolution procedure.
Though under the hood recalc is powered by a complex type system, you don’t have
to think about it that way. recalc
provides syntactic restrictions such that
programs do not grow too complex.
Formulas react to values they reference as usual. However, and this is unique to
recalc
, a spreadsheet is a function of time: values in recalc
may change
over time (see next section).
Spreadsheets that do not contain time-dependent values behave like regular spreadsheets would.
For example, the formula =LAST(@Temperature, 0)
only makes sense when we
specify a start and end time. Here @Temperature
has the type @number
(event
type) and is a discrete sampling of an external signal. LAST
evaluates to the
last occurence of its event (or the provided default when no event occurred
yet). Thus LAST(@Temperature, 0)
is a continuously defined signal (type
~number
).
Functional reactive programming is facilitated by the following primitives:
lift(v)
— lifts a static value v
to a time-varying signal.
Its type is {t} -> t -> t~
.reduce(~Event, v, fn)
— a generic stream reducer (an effectful fold), which
accumulates using fn
over ~Event
s using v
as a starting value.
Its type is {v} -> v -> {t} -> Event<t> -> (t -> v -> v) -> v~
.All interesting time-series functions can be implemented on top of the two. For
example, the LAST
combinator would be
\e -> \v -> reduce(e, v, \_ -> \last -> last)
The user interface for recalc
is a regular spreadsheet editor. You can view shortcuts using
Ctrl + \
.
At the bottom right, you’ll find a time slider that sets the start and end of your current time window. Drag to adjust, or double-click to type precise dates.
This controls how your stream-based computations are evaluated, as described in the "Functional Reactive Programming" section.