v3 Language: Collections and Closures

Lesson, slides, and applied problem sets.

View Slides

Lesson

v3 Language: Closures, Arrays, Maps

Why this module exists

This is the first module where language features require a real runtime contract:

  • first-class function values
  • heap-backed containers
  • indexed access/mutation semantics
  • closures over lexical environments

Without explicit rules, syntax appears to work while semantics drift.


1) Composite literals and evaluation order

Arrays

[1, 2, 3]

Evaluation is left-to-right for each element expression.

Maps

{"a": 1, "b": 2}

Evaluation is left-to-right by pair order. For each pair:

  • evaluate key
  • evaluate value
  • insert/overwrite pair in map

In this pack, map keys are constrained to hashable primitive kinds (string, number, bool, nil). Attempts with non-hashable runtime keys are rejected.


2) Indexing as a read/write primitive

Read form:

value = xs[0]

Write form:

xs[1] = 42
m["a"] = 99

Semantics to keep explicit:

  • container expression is evaluated first
  • index/key expression is evaluated next
  • for assignment, RHS is evaluated last

That ordering matters for side effects (e.g. f()[0] = g() must preserve call order).


3) Functions become values

In v3, function calls are expression-capable:

let f = makeAdder(10)
let z = f(2)

The key shift:

  • function declarations are bindings
  • those bindings can be passed, returned, and stored
  • call site expects callable value semantics, not just identifier lookup

Compiler/VM responsibilities:

  • closure/lambda creation emits callable value
  • CALL runtime instruction validates callee is callable
  • arity is still validated by analyzer when possible

4) Nested functions and lexical capture

Nested function example:

fn makeAdder(x) {
  fn add(y) { return x + y; }
  return add;
}

add captures x from defining scope, not from caller scope later.

Why this matters:

  • same function can be returned and later invoked
  • captured variable must remain valid while closure exists
  • this directly motivates heap and GC in v3

5) Runtime-facing rules for container values

Arrays/maps/closures are not immutable scalars:

  • aliasing is possible
  • writes can mutate shared values
  • equality is identity in this pack, not deep structural equality

Therefore:

  • arr1 == arr2 is false unless they reference the same object
  • passing arrays to functions shares a reference-like behavior

This behavior must be documented before students assume copy-on-write semantics.


6) Memory model transition from v2 to v3

v3 introduces heap-lifecycle requirements:

  • these values are allocated in the VM-managed heap
  • garbage collection must be able to recover unreachable objects
  • reference graphs can span frames through closures and captured environments

That is why v3 is the first true “memory-correctness” checkpoint.


7) Practice checkpoints (almost solved)

1) Array evaluation + [f(), g(), h()] should evaluate f then g then h.

2) Map key restrictions + {[1, 2]: 3} is invalid in this pack because array keys are not hashable.

3) Function value call + (fn add(y) { return y + 1; })(41) is legal if function expressions are supported; if not, the expected language rule is explicit in grammar.

4) Closure capture behavior + let x = 1; let f = fn() { return x }; x = 2; f()

Expected: f() observes either captured snapshot or live outer value depending on your capture strategy; this pack uses visible lexical value through environment semantics, so call-site expectations should be stated explicitly.

5) Index assignment side effects + xs[0] = xs[0] + 1 evaluates RHS using old xs[0], then writes back.

6) Equality rule + [1,2] == [1,2] is false under identity semantics.

The language is now about runtime truth and explicit value contracts, not just new syntax sugar.


Module Items

  • Program Lexer v3

    Lex the v3 language with arrays, maps, and positions.

    medium Sign in to access medium and hard problems
Join Discord