Language Features
Language features: an overview of syntax, operators, functions, and more
Program Structure
A Squiggle program consists of a series of definitions (for example, x = 5
, f(x) = x * x
). This can optionally conclude with an end expression.
If an end expression is provided, it becomes the evaluated output of the program, and only this result will be displayed in the viewer. Otherwise, all top-level variable definitions will be displayed.
Immutability
All variables in Squiggle are immutable, similar to other functional programming languages like OCaml or Haskell.
In the case of container types (lists and dictionaries), this implies that an operation such as myList[3] = 10 is not permitted. Instead, we recommend using List.map
, List.reduce
or other List functions.
In case of basic types such as numbers or strings, the impact of immutability is more subtle.
Consider this code:
While it appears that the value of x has changed, what actually occurred is the creation of a new variable with the same name, which shadowed the previous x variable.
In most cases, shadowing behaves identically to what you'd expect in languages like JavaScript or Python.
One case where shadowing matters is closures:
In the above example, the argPlusX
function captures the value of x
from line 1, not the newly shadowed x
from line 4. As a result, argPlusX(5)
returns 10, not 15.
Unit Type Annotations
Variable declarations may optionally be annotated with unit types, such as kilograms
or dollars
. Unit types are declared with ::
, for example:
A unit type can be any identifier, and you don't have to define a unit type before you use it.
You can also create composite unit types using *
(multiplication), /
(division), and '^' (exponentiation). For example:
You can use any number of *
and /
operators in a unit type, but you cannot use parentheses. Unit type operators follow standard order of operations: *
and /
are left-associative and have the same precedence, and ^
has higher precedence.
The following unit types are all equivalent: kg*m/s^2
, kg*m/s/s
, m/s*kg/s
, m/s^2*kg
, kg*m^2/m/s/s
.
For unitless types, you may use a number in the unit type annotation (by convention you should use the number 1
):
If you use unit type annotations, Squiggle will enforce that variables must have consistent unit types.
If a variable does not have a unit type annotation, Squiggle will attempt to infer its unit type. If the unit type can't be inferred, the variable is treated as any type.
Inline unit type annotations are not currently supported (for example, x = (y :: meters)
).
Operators and functions obey the following semantics:
- Multiplying or dividing two unit-typed variables multiplies or divides their unit types (respectively).
- Raising a unit-typed variable to a power produces a result of any unit type (i.e., the result is not type-checked).
- Most binary operators, including
+
,-
, and comparison operators (==
,>=
, etc.), require that both arguments have the same unit type. - Built-in functions can take any unit type and return any unit type.
Blocks
Blocks are special expressions in Squiggle that can contain any number of local definitions and end with an expression.
Conditionals
If/then/else statements in Squiggle are values too.
See Control flow for more details and examples.
Comments
Pipes
Squiggle features data-first pipes. Functions in the standard library are organized to make this convenient.
Standard Library
Squiggle features a simple standard libary.
Most functions are namespaced under their respective types to keep functionality distinct. Certain popular functions are usable without their namespaces.
For example,
Simple Error Handling
Squiggle supports the functions throw and try for simple error handling. It does not yet have proper error types.