Flow

Collections

Flow ships two general-purpose collection types: arrays (ordered, indexable, homogeneous) and dictionaries (Dict<K, V>, insertion-order preserved). Most collection functions require use "@std" (which transitively imports @collections).

Creating Arrays

use "@std"

Int[] nums = (list 1 2 3 4 5)
String[] names = (list "Alice" "Bob" "Charlie")
Int[] empty = (list)

Note: Array literal syntax also works — comma OR space-separated
Int[] inline   = [10, 20, 30]
Int[] spaceSep = [10 20 30]
Open in playground

Array Indexing

Use @ to access elements by index (0-based). Negative indices count from the end:

Int[] nums = (list 10 20 30)
Int first = nums@0    Note: 10
Int second = nums@1   Note: 20
Int last = nums@-1    Note: 30
Open in playground

Inspection

use "@std"

Int[] nums = (list 1 2 3 4 5)

Int count    = (length nums)        Note: 5 (alias: len)
Bool isEmpty = (empty nums)         Note: false
Bool has3    = (contains nums 3)    Note: true
Open in playground

Accessing Elements

use "@std"

Int[] nums = (list 1 2 3 4 5)

Int first = (head nums)   Note: 1
Int last = (last nums)    Note: 5
Open in playground

Slicing

use "@std"

Int[] nums = (list 1 2 3 4 5)

Int[] rest = (tail nums)         Note: [2, 3, 4, 5]
Int[] front = (init nums)        Note: [1, 2, 3, 4]
Int[] firstTwo = (take nums 2)   Note: [1, 2]
Int[] lastThree = (drop nums 2)  Note: [3, 4, 5]
Int[] middle = (slice nums 1 4)  Note: [2, 3, 4] (half-open)
Open in playground

Building Arrays

use "@std"

Int[] nums = (list 1 2 3)

Int[] withFour = (append nums 4)      Note: [1, 2, 3, 4]
Int[] withZero = (prepend 0 nums)     Note: [0, 1, 2, 3]
Int[] doubled = (concat nums nums)    Note: [1, 2, 3, 1, 2, 3]
Int[] flipped = (reverse nums)        Note: [3, 2, 1]
Open in playground

Range

use "@std"

Int[] oneToFive = (range 1 6)        Note: [1, 2, 3, 4, 5] (half-open)
Int[] evens     = (range 0 20 2)     Note: [0, 2, ..., 18] (3-arg step)
Open in playground

Zip

use "@std"

Int[] a = (list 1 2 3)
Int[] b = (list 10 20 30)
Int[][] zipped = (zip a b)    Note: [[1,10], [2,20], [3,30]]
Open in playground

Higher-Order Functions

map

Transform each element:

use "@std"

Int[] nums = (list 1 2 3 4 5)
Int[] doubled = (map nums (fn Int n => (mul n 2)))
(print (str doubled))  Note: [2, 4, 6, 8, 10]
Open in playground

filter

Keep elements matching a predicate:

use "@std"

Int[] nums = (list 1 2 3 4 5)
Int[] big = (filter nums (fn Int n => (gt n 3)))
(print (str big))  Note: [4, 5]
Open in playground

reduce

Fold an array with an accumulator:

use "@std"

Int[] nums = (list 1 2 3 4 5)
Int total = (reduce nums 0 (fn Int acc, Int n => (add acc n)))
(print (str total))  Note: 15
Open in playground

each

Apply a function to each element for side effects:

use "@std"

(each (list 10 20 30) (fn Int n => (print (str n))))
Note: prints 10, 20, 30 on separate lines
Open in playground

Combining Operations

Use the flow operator to chain collection operations:

use "@std"

Int[] nums = (list 1 2 3 4 5 6 7 8 9 10)

Note: Filter even numbers, double them, sum them
Int[] evens   = (filter nums (fn Int n => (equals 0 (sub n (mul (idiv n 2) 2)))))
Int[] doubled = (map evens (fn Int n => (mul n 2)))
Int total     = (reduce doubled 0 (fn Int acc, Int n => (add acc n)))
(print (str total))
Open in playground

Dictionaries (Dict<K, V>)

Dict<K, V> is a generic map that preserves insertion order. Allowed key types: Int, Long, Float, String, Symbol, Note, Chord, Beat, and tuples whose components are themselves hashable. Values are unrestricted.

Creating a Dict

use "@std"

Note: Alternating key, value, key, value, ...
Dict<Symbol, Int> bpms = (dict #verse 120 #chorus 140 #bridge 100)

Note: From tuples of <<K, V>>
Dict<String, Note> roots = (dictTuple <<"verse", C4>> <<"chorus", G4>>)
Open in playground

14-Op Surface

OpSignatureDescription
dict(... K V K V) -> Dict<K, V>Construct from key/value pairs
dictTuple(... <<K, V>>) -> Dict<K, V>Construct from tuple pairs
get(Dict, K) -> VFetch value (errors if missing)
getOr(Dict, K, V) -> VFetch value or fall back to default
set(Dict, K, V) -> DictReturn new dict with key set
remove(Dict, K) -> DictReturn new dict with key removed
has(Dict, K) -> BoolKey exists?
keys(Dict) -> K[]Insertion-ordered keys
values(Dict) -> V[]Insertion-ordered values
size(Dict) -> IntNumber of entries
merge(Dict, Dict) -> DictRight-biased merge
each(Dict, (K, V) => Void) -> VoidIterate for side effects
map(Dict, (K, V) => U) -> Dict<K, U>Transform values
filter(Dict, (K, V) => Bool) -> DictKeep entries matching predicate

Examples

use "@std"

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

Int v = (get bpms #verse)               Note: 120
Int fb = (getOr bpms #outro 100)        Note: 100 (fallback)
Dict<Symbol, Int> bigger = (set bpms #outro 90)
Dict<Symbol, Int> withoutChorus = (remove bpms #chorus)

(each bpms (fn Symbol k, Int v => (print $"{k} = {v}")))
Open in playground

Overloaded with Arrays

each, map, filter, and size are overloaded on both arrays and dicts — the resolver dispatches on argument types. The same function call shape works for either collection.

Tuples (Brief)

Tuples (<<a, b, c>>) live in Language Basics — they’re a fixed-arity, heterogeneous shape distinct from arrays. They support per-position types, structural equality, destructuring assignment (<<x, y>> = expr), and the tuple-unpack flow operator ~>.

Varargs and Plural Type Notation

Flow has two mechanisms for working with variable-length argument lists in function declarations.

Plural Form (Array Type Sugar)

Adding s to any type name creates an array type:

use "@std"

Note: These two declarations are equivalent:
Int[] nums = (list 1 2 3)
Ints nums2 = (list 4 5 6)

Note: Works for any type:
Strings names = (list "Alice" "Bob")
Notes melody = (list C4 D4 E4)
Voids anything = (list 1 "two" true)
Open in playground

In proc parameters, the plural form declares an array parameter — the caller must pass an array:

proc sumAll (Ints: numbers)
    (reduce numbers 0 (fn Int acc, Int n => (add acc n)))
end proc

Int[] data = (list 1 2 3 4 5)
Int total = (sumAll data)  Note: pass an array
Open in playground

Ellipsis Varargs (...)

The ... syntax after a type in proc parameters declares a varargs parameter — the function collects remaining arguments into an array:

proc showAll (Void...: items)
    (each items (fn Void item => (print (str item))))
end proc

(showAll 1 "two" true)  Note: pass individual arguments
Open in playground

Key Difference

SyntaxIn ParameterCaller Passes
Ints: xArray parameter (Int[])A single array value
Int...: xVarargs parameterIndividual arguments, collected into array

The standard library uses both. For example:

  • list uses Void...: items — call (list 1 2 3) with individual args
  • head uses Voids: arr — call (head myArray) with an array

Plural Forms Reference

SingularPlural (Array)
IntInts = Int[]
StringStrings = String[]
DoubleDoubles = Double[]
BoolBools = Bool[]
NoteNotes = Note[]
VoidVoids = Void[] (any array)
BufferBuffers = Buffer[]
SequenceSequences = Sequence[]

Complete Array Function Reference

FunctionSignatureDescription
list(...T) -> T[]Create array from arguments
length / len(T[]) -> IntArray length
head(T[]) -> TFirst element
tail(T[]) -> T[]All except first
last(T[]) -> TLast element
init(T[]) -> T[]All except last
empty(T[]) -> BoolIs array empty?
reverse(T[]) -> T[]Reverse order
take(T[], Int) -> T[]First N elements
drop(T[], Int) -> T[]Drop first N
slice(T[], Int, Int) -> T[]Half-open slice
append(T[], T) -> T[]Add to end
prepend(T, T[]) -> T[]Add to start
concat(T[], T[]) -> T[]Concatenate arrays
contains(T[], T) -> BoolElement exists?
map(T[], T => U) -> U[]Transform elements
filter(T[], T => Bool) -> T[]Filter by predicate
reduce(T[], U, (U, T) => U) -> UFold with accumulator
each(T[], T => Void) -> VoidApply for side effects
range(Int, Int[, Int]) -> Int[]Half-open integer range with optional step
zip(T[], U[]) -> [T,U][]Pair elements

See Also