Flow

Language Basics

Flow is a statically-typed, interpreted language. Every variable has a type, statements are separated by newlines or semicolons, and there are five comment styles for different positions in a line.

Comments

Flow accepts five comment markers. All five run from their marker to the end of the line:

Note: This is a comment (must start the line content)
TODO: also a comment to EOL (start of line content)
FIXME: same — recognized at line start
// This is also a comment, anywhere on the line
; A Lisp-style line comment — must be at column 0 (mid-line `;` is a statement separator)
Int x = 5  Note: inline comment
Int y = 7  // inline comment
Open in playground

Note:, TODO:, FIXME:, and column-0 ; must start at the beginning of a line’s content (leading whitespace allowed). // works anywhere on the line.

A backslash at end-of-line is a line continuation — it joins the next physical line into the current logical line while preserving line numbers for diagnostics:

Int total = (add 1 \
             (add 2 \
              3))
Open in playground

Variables

Variables are declared with a type annotation:

Int x = 5
Float pi = 3.14
String name = "Flow"
Bool active = true
Symbol tag = #verse
Open in playground

Default Values

A variable declared without an initializer takes a type-appropriate default (0, "", false, empty array, etc.):

Int count           Note: count = 0
String message      Note: message = ""
Bool flag           Note: flag = false
Int[] nums          Note: nums = []
Open in playground

Reassignment

Variables can be reassigned after declaration. Use the prefix arithmetic builtins for math (Flow has no infix + - * /):

use "@std"

Int x = 10
x = 20
x = (add x 5)
(print (str x))     Note: prints 25
Open in playground

Semicolons

Statements are separated by newlines. Semicolons allow multiple statements on one line:

Int a = 1; Int b = 2; Int c = 3
(print (str a)); (print (str b)); (print (str c))
Open in playground

Primitive Types

TypeDescriptionExample
Int32-bit integer42
Long64-bit integer-
Float32-bit float3.14
Double64-bit float3.14
StringText"hello"
BoolBooleantrue, false
NumberArbitrary precision (BigInteger)-
SymbolInterned identity literal#foo
LazyDeferred / memoized expressionlazy (expr)
FunctionCallable valuefn Int n => (mul n 2)
BufferAudio samples-

Numeric Widening

Flow supports implicit numeric widening:

Int → Long → Float → Double → Number
Open in playground

An Int can be used wherever a Double is expected. Integer literals that exceed Int range automatically fall through to Long, then BigInteger (Number) — no overflow error.

Special (Music) Types

TypeDescriptionExample
NoteMusical pitchC4, F4+ (F sharp)
ChordHarmonic chordCmaj7, Dm
SequenceOrdered bars\| C4 D4 E4 F4 \|
BarMusical measure-
SectionNamed song partsection intro { ... }
SongArrangement[intro verse chorus]
SemitonePitch offset+2st, -3st
CentMicrotonal offset+50c, -25c
MillisecondTime in ms100ms
SecondTime in seconds2.5s
DecibelGain in dB+6dB, -12dB
HertzFrequency440Hz, 1.5kHz
BeatMusical beat-
NoteValueNote duration-
TimeSignatureMeter-
MusicalNoteNote with duration-
VoicePositioned audio-
TrackVoice collection-
EnvelopeAmplitude shape-
TuningScala tuning (reference identity)(loadScala "x.scl")
SfzSFZ sampler patch (reference identity)(loadSfz #violin)
MarkovModel / LsystemModelGenerative models (reference identity)(markovTrain ...)

Note Literals

Notes use [A-G][octave][alteration]:

SyntaxMeaning
C4C natural, octave 4 (middle C)
C4+C sharp, octave 4
C4-C flat, octave 4
C4++C double sharp
C4--C double flat
C4+50cC4 + 50 cents (microtonal)

Chord-symbol accidentals use s and f instead (for example, Csmaj7, Bfm). See Chords and Harmony.

Symbols

A Symbol is an interned identity literal — like Ruby’s :foo or Clojure’s :foo. Two #foo literals always compare equal by reference, and a Symbol is strictly distinct from the string "foo":

use "@std"

Symbol style = #jazz
Symbol same = #jazz
(print (str (equals style same)))     Note: true

Note: Symbols and Strings are NOT interchangeable
(print (str (equals #foo "foo")))     Note: false
Open in playground

Symbols are the natural choice for tags, enum-like flags, and Dict keys.

Arrays

Arrays are typed collections:

use "@std"

Int[] nums = (list 1 2 3 4 5)
String[] names = (list "Alice" "Bob" "Charlie")
Int[] inline = [10, 20, 30]      Note: array literal — comma OR space separated
Open in playground

Array indexing uses @ (supports negative-from-end):

Int first = nums@0
Int last = nums@-1
Open in playground

Plural type names are shorthand for arrays: Ints = Int[], Notes = Note[], Strings = String[], Voids = Void[] (any), etc.

See Collections for full array (and Dict) operations.

Tuples

Tuples group fixed-arity heterogeneous values. They’re written with << and >>, with per-position types:

use "@std"

<<Int, Int>> point = <<3, 4>>
<<String, Note, Bool>> mix = <<"snare", C4, true>>

Note: Empty and singleton arities are both valid
<<>> nothing = <<>>
<<Int>> solo = <<42>>

Note: Index with @N
Int x = point@0
Int y = point@1
Open in playground

Tuples compare by structural equality ((equals <<1, 2>> <<1, 2>>) is true).

Destructuring Assignment

Unpack a tuple into named bindings with <<...>> =:

<<Int x, Int y>> = <<3, 4>>
(print $"x={x} y={y}")

Note: Per-slot types are optional
<<a, b, c>> = <<1, "two", true>>
Open in playground

Tuple-Unpack with ~>

The ~> flow operator unpacks a tuple into a multi-arg call. Non-tuple LHS falls through to plain -> semantics:

proc renderHit (Note: pitch, Note: dur)
    (print $"hit: {pitch} {dur}")
end proc

<<Note, Note>> entry = <<C4, D4>>
entry ~> renderHit                    Note: equivalent to (renderHit C4 D4)
Open in playground

There’s also a runtime equivalent — (unpack tuple func) — which mirrors Lisp’s (apply f args). See Flow Operator.

Dictionaries

Generic Dict<K, V> preserves insertion order. Allowed key types: Int, Long, Float, String, Symbol, Note, Chord, Beat, and tuples of any of these.

use "@std"

Dict<Symbol, Int> bpms = (dict #verse 120 #chorus 140 #bridge 100)

Int v   = (get bpms #verse)            Note: 120
Int fb  = (getOr bpms #outro 80)       Note: 80 (default)
Dict<Symbol, Int> bigger = (set bpms #outro 90)
Bool has = (has bpms #verse)           Note: true
Int n   = (size bpms)
Symbol[] ks = (keys bpms)
Int[] vs    = (values bpms)
Open in playground

dict takes alternating key/value pairs; dictTuple takes <<K, V>> tuples instead. Higher-order ops (each, map, filter) and size are overloaded — they work on both arrays and dicts; the resolver picks based on argument types.

See Collections for the full 14-op surface.

Arithmetic and Other Operators

Arithmetic

Arithmetic is prefix-only via standard-library builtins — there are no infix + - * / operators (the parser will reject stray infix and suggest the prefix form).

use "@std"

Int sum  = (add 3 4)       Note: 7
Int diff = (sub 10 3)      Note: 7
Int prod = (mul 5 6)       Note: 30
Double q = (div 15 4)      Note: 3.75 — (div Int Int) auto-promotes to Double
Int qi   = (idiv 15 4)     Note: 3 — integer (truncating) division
Int neg  = (neg 5)         Note: -5

String greeting = (concat "Hello, " "World!")
Open in playground

Negative numeric literals (-3, -2.5, -12dB) lex as single tokens at expression-start positions (after (, ,, =, |, etc.) — (add x -3) works as written.

Comparison

Comparisons are function calls:

use "@std"

Bool eq  = (equals 5 5)      Note: true (compatible types)
Bool seq = (sequals 5 5)     Note: true (strict — types must match exactly)
Bool lt  = (lt 3 5)          Note: true
Bool gt  = (gt 10 5)         Note: true
Bool lte = (lte 3 3)         Note: true
Bool gte = (gte 5 3)         Note: true
Open in playground

Logical

use "@std"

Bool a = (and true false)    Note: false
Bool b = (or true false)     Note: true
Bool c = (not true)          Note: false
Open in playground

and and or accept Lazy arguments for short-circuit evaluation.

Control Flow

Conditional (if)

if is a function that takes a boolean and two lazy expressions. You must wrap both branches with lazy ():

use "@std"

Int x = 10

Note: returning a value
String result = (if (gt x 5) lazy ("big") lazy ("small"))
(print result)    Note: "big"

Note: side-effect branches
(if (gt x 5) lazy ((print "big")) lazy ((print "small")))
Open in playground

Without lazy, arguments are eagerly evaluated and the overload won’t match.

Pattern Matching

match is the structured alternative to chained ifs. It scrutinizes a value against one or more arms:

use "@std"

Int n = 3
String label = (match n
                | 0 => "zero"
                | 1 => "one"
                | _ => "many")
(print label)

Note: Music-aware patterns — chord literals match by chord identity
String roleName = (match Cmaj7
                   | Cmaj7 => "tonic"
                   | Dm    => "ii"
                   | _     => "other")

Note: Binding patterns capture the matched value
String sign = (match n
               | x when (lt x 0) => "negative"
               | 0               => "zero"
               | x               => "positive")
Open in playground

Patterns supported: _ wildcard, literal patterns (Int / Float / String / Bool / Note), binding patterns (bare identifier), constructor patterns (chord literals, roman numerals, articulation symbols), and when (...) guards.

To make non-exhaustive matches a hard error instead of a warning, add this pragma at the top of the file:

enable matchExhaustive;
Open in playground

Loops

Flow supports for and while loops with break and continue:

use "@std"

Int sum = 0
for Int n in (list 1 2 3 4 5) {
    sum = (add sum n)
}

Int count = 0
while (lt count 5) {
    count = (add count 1)
}
Open in playground

Loops have a configurable maximum iteration count (default 10000) to prevent runaway execution — call (setMaxIterations N) to raise it. See Loops for the full reference.

Lazy Evaluation

lazy (expr) creates a deferred value (a thunk) that is not evaluated until forced with eval. The result is memoized:

use "@std"

Lazy<Void> deferred = lazy ((print "hello"))
Note: nothing printed yet
(eval deferred)    Note: now prints "hello"
Open in playground

if, and, and or accept Lazy parameters to enable short-circuit evaluation.

String Interpolation

Prefix a string with $ and wrap expressions in { }:

use "@std"

Int x = 42
(print $"x is {x}")                  Note: x is 42

Int a = 3
Int b = 4
(print $"sum is {(add a b)}")        Note: sum is 7
Open in playground

See String Interpolation for details (including \{ / \} escapes).

Named Arguments

Any function with named parameters can be called using name=value syntax. Positional and named args can mix, but every positional arg must precede every named one:

use "@std"
use "@improv"

Sequence chords = | Cmaj7 Am7 Dm7 G7 |

Note: All named
Sequence solo = (jam over=chords style=#jazz length=8 seed=42)

Note: Mixed — positional first
Sequence solo2 = (jam chords style=#blues length=16)
Open in playground

About 150 builtin signatures have parameter names backfilled. See Functions.

Function Type Annotations

Use parenthesized arrow syntax for precise function types:

(Int => Int) doubler = fn Int n => (mul n 2)
(Int, Int => Int) adder = fn Int a, Int b => (add a b)
(Void => Int) constant = fn => 42
Open in playground

Type Aliases (Plural Shorthand)

Any type name ending in s refers to the array form: Ints = Int[], Strings = String[], Voids = Void[], Notes = Note[], etc.

Scoping

Variables declared inside blocks (functions, musical context blocks, sections, loops) are scoped to that block. Inner scopes can access variables from outer scopes (lexical scope).

Proc calls use call-boundary frames: a proc body cannot read or write its caller’s locals — only its own parameters, locals, and globals are in scope. Globals remain readable and writable from any proc. Lambdas capture by snapshot at the point they are created (lexical capture, not dynamic lookup).

Note: quoted string literals are always String values — "10s" is the string "10s", not a Second. Music-typed values come only from bare tokens: 10s, C4, -3dB, 440Hz, etc.

Pragmas

Pragmas are file-scope toggles at the top of a file. Each module has its own PragmaSet — pragmas don’t leak across use imports:

enable hAsB;             Note: H aliases to B in note streams (German notation)
enable justIntonation;   Note: 5-limit JI tuning rooted at active key tonic
enable matchExhaustive;  Note: promote non-exhaustive match warnings to errors
Open in playground

Unknown pragmas error with a did-you-mean suggestion.

See Also