FREAKV3 docs freak 0.14.2 (Maverick)

Lists & arrays#

> V3 has two collection surfaces: a typed List<T>, and an older untyped handle. Know which one you are holding.

This is the one part of V3 with genuine overlap, because List<T> was added on top of a pre-existing untyped array. Both still work, and they are not interchangeable everywhere.

List<T>Legacy handle
TypeList<int>, List<word>, List<Shape>, …int
Created by[a, b, c], List::new(), List::with_capacity(n), List::filled(v, n)array_new()
Element typeCheckedAlways word
Lengthlist.length()array_len(h)
Readlist[i]array_get(h, i)
Writelist[i] = v (needs pilot mut)array_set(h, i, v)
Appendlist.push(v)array_push(h, v)
Removelist.pop()
Accepted by array_* builtinsyes, when List<word>yes
Accepted by std/algorithm.fk tasksnoyes

The List type#

The element type must be a scalar — int, num, word, bool — or the name of a shape. There is no nesting: List<List<int>> does not parse.

examples/lists.fk compilesruns
-- `List<T>` is V3's typed sequence. The element type must be a scalar
-- (`int`, `num`, `word`, `bool`) or the name of a shape. Nesting is not
-- allowed: there is no `List<List<int>>`.
shape Contact {
    tag: word
}

-- A List may be a parameter and a return type. It may NOT be a shape
-- field: that reports "V3 owned shape fields do not yet support List
-- values".
task count_all(items: List<word>) -> int {
    give back items.length()
}

task first_three() -> List<int> {
    give back [1, 2, 3]
}

task main() -> void {
    -- An array literal infers List<T> from its elements.
    pilot names: List<word> = ["Takeru", "Meiya", "Sumika"]
    say word_from_int(names.length())
    say names[1]

    -- Indexed assignment requires `pilot mut`.
    pilot mut scores: List<int> = [10, 20, 30]
    scores[0] = 99
    say word_from_int(scores[0])

    -- Every scalar element type works.
    pilot ratios: List<num> = [1.5, 2.5]
    say format_num(ratios[1])

    pilot flags: List<bool> = [true, false]
    say word_from_bool(flags[0])

    -- Shapes too, and field access chains off the element.
    pilot contacts: List<Contact> = [Contact { tag: "BETA-1" }]
    say contacts[0].tag

    -- List::filled(value, count) builds a pre-populated list.
    pilot zeros: List<int> = List::filled(0, 4)
    say word_from_int(zeros.length())

    -- Passed to a task, and returned from one.
    say word_from_int(count_all(names))
    say word_from_int(first_three()[2])

    -- Lists grow as of v0.14.2. Mutating methods need `pilot mut`.
    pilot mut queue: List<int> = List::new()
    queue.push(10)
    queue.push(20)
    queue.push(30)
    say word_from_int(queue.length())
    say word_from_int(queue.pop())      -- 30, removed and returned
    say word_from_int(queue.length())
    say word_from_int(queue.capacity()) -- starts at 8, doubles as needed
    queue.reserve(64)
    say word_from_int(queue.capacity())
    queue.clear()
    say word_from_int(queue.length())

    -- Iterate with an index; there is no `for each`.
    pilot mut i: int = 0
    repeat names.length() times {
        say names[i]
        i += 1
    }
}
Program output
3
Meiya
99
2.5
true
BETA-1
4
3
3
3
30
2
8
64
0
Takeru
Meiya
Sumika

Rules worth pinning down:

  • **[a, b, c] infers List<T>** from its elements, all of which must share one type. Numbers stay numbers; no conversion to word is needed.
  • **.length() is a method**, not a field. list.length reports "non-shape value has no fields".
  • **Indexed assignment needs pilot mut.** Otherwise you get "indexed assignment requires a mutable list binding; declare it with pilot mut".
  • **List::filled(value, count)** builds a pre-populated list and takes its element type from value.
  • Indexing a shape element chains: contacts[0].tag.

Growing a list#

As of v0.14.2 lists grow. This is the biggest change to the type since it was introduced, and it removes most reasons to reach for the legacy handle.

MethodSignatureNotes
.push(value)T -> voidAppend
.pop()-> TRemove and return the last element
.length()-> int
.capacity()-> intAllocated slots; starts at 8 and doubles
.reserve(n)int -> voidGrow the capacity up front
.clear()-> voidLength to zero, capacity kept
FREAK
pilot mut xs: List<int> = List::new()
xs.push(10)
xs.push(20)
say word_from_int(xs.pop())     -- 20
say word_from_int(xs.length())  -- 1

Constructors: List::new(), List::with_capacity(n), List::filled(v, n), or a literal.

Mutating methods need pilot mut, same as indexed assignment.

Still missing: insert, remove, sort, and any iterator. Sorting a list means copying into a legacy handle, or writing the loop.

Where a List may appear#

PositionAllowed
pilot / fixed pilot bindingyes
Task parameteryes
Task return typeyes
Shape fieldno
Careful

A shape field cannot hold a list: ``text type error: V3 owned shape fields do not yet support List values ` Store a legacy int` array handle in the field instead, or keep the list in a separate binding beside the shape.

Note

With push/pop landed, List<T> is now the default choice. The legacy handle is worth keeping for two things only: reaching the std/algorithm.fk helpers, which are declared handle: int, and storing a collection in a shape field, which List<T> still cannot do.

The legacy handle#

array_new() returns an int handle to a runtime array whose elements are always word. Store other types by converting them in and out.

examples/arrays.fk compilesruns
-- V3 arrays are runtime handles of `word` elements, addressed by an
-- `int` handle. Store other types by converting them to `word`.
task main() -> void {
    pilot xs: int = array_new()

    array_push(xs, "alpha")
    array_push(xs, "bravo")
    array_push(xs, "charlie")

    say word_from_int(array_len(xs))
    say array_get(xs, 0)
    say array_get(xs, 2)

    array_set(xs, 1, "BRAVO")
    say array_get(xs, 1)

    -- Iterate with an index and a counted loop.
    pilot mut i: int = 0
    repeat array_len(xs) times {
        say array_get(xs, i)
        i += 1
    }

    array_release(xs)
}
Program output
3
alpha
charlie
BRAVO
alpha
BRAVO
charlie
CallSignature
array_new()-> int
array_push(h, value)word-array, word -> void
array_get(h, i)word-array, int -> word
array_set(h, i, value)word-array, int, word -> void
array_len(h)word-array -> int
array_release(h)word-array -> void
word_join(h)int -> word

word-array is a checker-internal marker meaning "either an int handle or a List<word>" — it is not a type you can write in source. That is why the array_* builtins accept both, while a user-declared task with handle: int accepts only the handle.

Mixing the two#

examples/array_literal.fk compilesruns
-- An array literal infers `List<T>` from its elements. Numbers stay
-- numbers -- there is no need to convert them to `word` any more.
--
-- The older untyped `array_*` handle still exists alongside it; see
-- arrays.html for when each one applies.
task main() -> void {
    pilot names: List<word> = ["Takeru", "Meiya", "Sumika"]
    say word_from_int(names.length())
    say names[1]

    pilot numbers: List<int> = [3, 1, 2]
    say word_from_int(numbers.length())
    say word_from_int(numbers[0])

    -- The array_* builtins accept a List<word> as well as a raw handle.
    say word_from_int(array_len(names))
    say array_get(names, 2)

    -- std/algorithm.fk tasks are declared with `handle: int`, so they take
    -- only the legacy handle. Build one with array_new() to use them.
    pilot h: int = array_new()
    array_push(h, word_from_int(3))
    array_push(h, word_from_int(1))
    array_push(h, word_from_int(2))

    array_sort_int(h)
    say array_join(h, ",")
    say word_from_int(array_sum_int(h))
    say word_from_int(array_max_int(h))
    say word_from_int(array_binary_search_int(h, 3))

    array_reverse(h)
    say array_join(h, "|")
    say word_from_bool(array_contains(h, "2"))

    array_release(h)
}
Program output
3
Meiya
3
3
3
Sumika
1,2,3
6
3
2
3|2|1
true

The rule that follows from the table above: builtins bridge, library tasks do not.

FREAK
pilot names: List<word> = ["b", "a"]

say word_from_int(array_len(names))   -- fine: builtin, accepts word-array
say array_get(names, 0)               -- fine

-- array_join is a task in std/algorithm.fk declared `handle: int`:
-- say array_join(names, ",")
--   type error: call to 'array_join' argument 1 expects int, got List<word>

If you need the library helpers, build a legacy handle with array_new().

Iterating#

There is no for each in either surface. Keep an index and use a counted loop:

FREAK
pilot mut i: int = 0
repeat items.length() times {
    say items[i]
    i += 1
}

repeat N times evaluates its count once, so growing the collection inside the loop will not extend the iteration.

std/algorithm.fk helpers#

Plain FREAK source, linked by freak build and freak run (not freak check). Every helper takes a **legacy int handle**.

TaskSignatureNotes
array_sort_int(h)int -> voidIn-place insertion sort, numeric order
array_binary_search_int(h, target)int, int -> intIndex or -1; needs a sorted array
array_reverse(h)int -> voidIn place
array_find(h, target)int, word -> intIndex or -1
array_contains(h, target)int, word -> bool
array_count(h, target)int, word -> int
array_copy(h)int -> intNew handle, shallow copy
array_join(h, sep)int, word -> word
array_sum_int(h)int -> int
array_max_int(h) / array_min_int(h)int -> int
Careful

Two of these are broken, re-confirmed against the compiler that verified this site: - array_sort_word(h) segfaults. It routes through extern task freak_word_compare(a: word, b: word) -> int, and that declaration does not bridge the LLVM backend's word handle representation. See extern & FFI. - array_unique(h) returns an empty array. To sort words, keep a parallel int key array and sort that, or write an insertion sort in your own code comparing with .char_at().

Memory#

array_release(h) frees a legacy array. V3 has no garbage collector and no automatic drop, so a long-running program that allocates in a loop must release. Do not use a handle after releasing it, and do not release twice.

List<T> values have no explicit release call.

Passing collections to tasks#

A legacy handle is just an int, so it passes freely and the callee mutates the caller's array — the standard way to return a collection:

FREAK
task fill(out: int) -> void {
    array_push(out, "alpha")
    array_push(out, "bravo")
}

task main() -> void {
    pilot items: int = array_new()
    fill(items)
    say word_from_int(array_len(items))
    array_release(items)
}

A List<T> can also be a parameter or return type, since parser_take_type accepts the annotation in both positions.

Maps and sets#

Still none. Map<K,V>, Set<T> and Lineup<T> do not exist, and the { "k": v } map literal does not parse. For small lookups, use two parallel collections and array_find.

Every FREAK snippet on this site was compiled by freak 0.14.2 (Maverick), built from source with a verified self-host fixed point. 45/45 examples compile; regenerate with python tools/verify.py then python tools/build_docs.py.