Skip to content

v0.40.0

Choose a tag to compare

@emil14 emil14 released this 31 Jul 20:18
· 82 commits to main since this release
284c5ca

Tidal Threads

Compared to v0.39.0, Neva v0.40.0 redesigns streams as an explicit tagged-union protocol, expands immutable list operations, and makes common scalar collections materially faster without changing their public Neva types.

Highlights

  • stream<T> is now the tagged union Open | Data T | Close, rather than a struct carrying an implicit index and terminal flag. Stream boundaries and payloads are explicit, and an index is created only when a program asks for one with streams.Enumerate<T>.
  • streams.Just<T> adapts one value to a stream, while streams.Enumerate<T> adds explicit zero-based indexes when they are needed.
  • lists.Append<T> is the renamed lists.Push<T> operation. Together with new Prepend<T> and Concat<T>, it provides immutable list updates.
  • Scalar list<T> and dict<T> values now use native typed runtime storage where possible. Scalar-list traversal is 75–87% faster; constructing a 512-int list is 82% faster, uses 64% less memory, and makes 255 fewer allocations.
  • neva tool <name> provides a consistent CLI entry point for an installed neva-* developer tool, including neva tool lsp.
  • Compiler-version mismatch errors now name the workspace module, point to its neva.yml or neva.yaml, and recommend neva upgrade.

Streams: a model with explicit states

Previously, every item in a stream<T> carried control metadata even when a program only needed the value:

pub type stream<T> struct {
    data T
    idx int
    last bool
}

Now a stream is a tagged union with exactly three possible states:

pub type stream<T> union {
    Open
    Data T
    Close
}

Open marks the beginning of a stream, Data carries a value, and Close marks completion. Sources such as streams.Range, streams.FromList, and streams.Just emit this protocol; ordinary user code normally passes streams to standard combinators rather than handling the three cases itself.

This is Neva's return to the canonical FBP stream design: bracket information packets delimit a sequence of ordinary data packets. It follows the model described by FBP pioneer J. Paul Morrison, with whom Neva's author corresponded while developing the language. The earlier indexed structure was an intermediate design; experience showed that the explicit FBP protocol is the more idiomatic and robust model.

streams.Map, Filter, ForEach, and other combinators use Switch<stream<T>> internally: they forward Open, transform or handle Data, and forward Close. The three paths are intentionally shown only as a schematic here — the actual Map graph also has a FanIn and ordering/backpressure wiring that must not be omitted:

Open  -> ... -> Open
Data  -> handler -> Data
Close -> ... -> Close

The model makes the protocol visible in the type system, removes a stored index from the default representation, and keeps indexing as an opt-in operation.

streams.Just<T>

pub def Just<T>(data T) (res stream<T>)

Just adapts one value to an API that expects a stream. Given 42, it emits Open, Data(42), then Close.

This matters when a higher-level API is stream-oriented but the caller has one value. For example, the PNG example creates one image.Pixel, turns it into a one-item stream with Just, and passes that stream to image.New:

new image.New
just streams.Just<image.Pixel>
---
newPixel -> just -> new

streams.Enumerate<T>

pub type Enumerated<T> struct {
    idx int
    item T
}

pub def Enumerate<T>(data stream<T>) (res stream<Enumerated<T>>)

Use Enumerate when an index is part of the program's logic. It attaches 0, 1, 2, … to Data items instead of making every stream item retain an index by default.

std/lists

This release adds three list operations. As with the rest of Neva's value operations, each returns a new list; the input list is not mutated.

lists.Append<T> — renamed from Push<T>

pub def Append<T>(lst list<T>, data T) (res list<T>)

The dataflow below appends 3 to [1, 2] and produces [1, 2, 3].

append lists.Append<int>
---
[1, 2] -> append:lst
3 -> append:data
append:res -> :res // produces [1, 2, 3]

lists.Prepend<T>

pub def Prepend<T>(lst list<T>, data T) (res list<T>)

Prepending 1 to [2, 3] produces [1, 2, 3].

lists.Concat<T>

pub def Concat<T>(left list<T>, right list<T>) (res list<T>)

Joining [1, 2] and [3, 4] produces [1, 2, 3, 4].

concat lists.Concat<int>
---
[1, 2] -> concat:left
[3, 4] -> concat:right
concat:res -> :res // produces [1, 2, 3, 4]

Faster scalar lists and dictionaries

This release includes a runtime optimization for common scalar collections. On the measured hot path, scalar-list traversal is 75–87% faster; constructing a 512-int list is 82% faster and uses 64% less memory. At the language level, list<T> and dict<T> are unchanged.

Internally, homogeneous scalar values no longer have to be stored as a generic runtime-message box per element when a typed representation is available.

Conceptually, the runtime can now keep an integer list or dictionary in native typed storage:

before: []Msg{Int(1), Int(2), Int(3)}
after:  []int64{1, 2, 3}

before: map[string]Msg{"answer": Int(42)}
after:  map[string]int64{"answer": 42}

Heterogeneous and non-scalar values retain the existing generic representation. The runtime boxes values only at boundaries that actually require generic messages.

On an Apple M1 Pro with Go 1.26.5, the benchmarked implementation delivers:

  • scalar-list traversal: 75–87% faster for 8–1024 integer items;
  • construction of a 512-int list: 82% faster, 64% fewer bytes, and 255 fewer allocations;
  • construction of a 512-int dictionary: 10.6% faster, 33.7% fewer bytes, and 255 fewer allocations;
  • full list_at and list_slice paths: 3–4% faster with one to two fewer allocations;
  • dictionary lookup: neutral in the full port path; no regression observed.

The detailed benchmark method and raw comparison are in PR #1127.

Developer tools

Run an installed language server through the Neva CLI:

neva tool lsp

neva tool is a dispatcher for installed executables named neva-<name>; it does not install or update tools itself.

Smaller developer-experience improvements

  • Compiler-version mismatch errors now identify the affected workspace module, show where its declared version lives, and suggest neva upgrade.
  • Tagged-union documentation now matches the compiler: declare tagged union { ... } types rather than the removed untagged-union syntax.

Included PRs

  • #1042 Redesign stream as union and add streams.Enumerate
  • #1047 Redesign stream item helpers and enumerate support
  • #1127 Typed scalar list/dict containers and runtime fast paths
  • #1144 Validate union literal payloads
  • #1151, #1153, #1154, #1155, #1157 Runtime message and collection work
  • #1158 CLI developer-tool manager
  • #1161 Tagged-union documentation correction
  • #1162 Compiler-version mismatch guidance

Documentation, CI, benchmark, and engineering-harness improvements in the same release window are included as maintenance work.

Full Changelog

v0.39.0...v0.40.0