neoCLR

IMPLEMENTATION NOTE · OPEN DESIGN

Ask for the access you need.

Sequence provides count and indexed read access. MutableSequence adds replacement; List adds growth. Arrays and lists can be consumed through these capabilities.

Development after Preview 8 · September 19, 2026. Collection capabilities are included in Preview 8. The development API adds a basic set of iterable operators and uses Filter and Map; Preview 8 packages still use Where and Select. These APIs remain open to feedback.

Collections and queries in Raven.

func Read(values: Sequence<int>) {
    WriteLine(values.Count)
    WriteLine(values[0])
    WriteLine(values.ToList().Count)
}

func Replace(values: MutableSequence<int>, value: int) {
    values[0] = value
}

func Append(values: List<int>) {
    values.Add(11)
}

The full sample reads both arrays and lists, replaces elements through MutableSequence, and grows a List. A Sequence view sees changes made through another alias: read-only access does not mean immutable storage.

Complete executable sample → · VS Code setup →

Filter, then map.

Import System.Linq.* for the query operators. Filter keeps matching elements; Map transforms them. This development sample prints 10, then 30. Neither operator invokes its callback until iteration begins.

import System.Linq.*
import System.Console.*

func Main() {
    let values: int[] = [1, 2, 3]
    let query = values.Filter(value => value != 2)
        .Map(value => value * 10)

    for value in query {
        WriteLine(value)
    }
}

Complete executable sample → · Expected output →

When moving from Preview 8, replace Where with Filter and Select with Map, then rebuild against the matching development references and runtime library. The old names are not aliases. First, Last, Single and ToList retain their names.

A basic operator set.

Development after Preview 8. Any and All test elements; Count counts them. Take and Skip select a page. Concat joins two sequences in order, and FlatMap turns each element into a sequence and flattens the results. Fold accumulates from an explicit seed, returning that seed for empty input.

import System.Collections.*
import System.Linq.*
import System.Console.*

public func Pair(value: int) -> Iterable<int> {
    return [value, value * 10]
}

func Main() {
    let values: int[] = [1, 2, 3, 4]
    let page = values.Skip(1).Take(2)
    for value in page {
        WriteLine(value)
    }

    if values.Any(value => value > 3) {
        WriteLine("True")
    }
    if values.All(value => value > 0) {
        WriteLine("True")
    }
    WriteLine(values.Count(value => value > 2))
    WriteLine(values.Fold(0, (total, value) => total + value))

    let extra: int[] = [5]
    for value in page.Concat(extra).FlatMap(value => Pair(value)) {
        WriteLine(value)
    }
}

The page contains 2 and 3. Both tests are true, the matching count is 2, and the total is 10. The final pipeline prints 2, 20, 3, 30, 5 and 50.

Complete executable sample → · Expected output →

Any and All stop as soon as the answer is known. Empty Any is false and empty All is true. Take, Skip, Concat and FlatMap are lazy. Non-positive Take yields nothing; non-positive Skip skips nothing. Fold consumes its input from left to right. These methods require the development library; they are not included in Preview 8 downloads.

From .NET LINQ to neoCLR.

This maps the current development API. It is not a promise of full LINQ compatibility; Preview 8 retains Where/Select and has fewer operators.

.NET EnumerableneoCLRBehavior
Where(predicate)Filter(predicate)Lazy filtering.
Select(selector)Map(selector)Lazy projection.
SelectMany(selector)FlatMap(selector)Lazy flattening; no indexed or result-selector overloads.
Any(), Any(predicate)Any(), Any(predicate)False for empty input; short-circuits.
All(predicate)All(predicate)True for empty input; short-circuits.
Count(), Count(predicate)Count(), Count(predicate)Int32 result; overflow is a runtime fault, not OverflowException.
Aggregate(seed, accumulator)Fold(seed, accumulator)Left-to-right, seeded accumulation; empty input returns the seed.
Take(count), Skip(count)Take(count), Skip(count)Lazy prefix/suffix; non-positive bounds follow .NET behavior.
Concat(second)Concat(second)Lazy concatenation in source order.
ToList()ToList()Materializes an ArrayList rather than a .NET List.
First(), First(predicate)First(), First(predicate)Returns Option; empty/no match is None rather than an exception.
Last(), Last(predicate)Last(), Last(predicate)Returns Option; empty/no match is None rather than an exception.
Single(), Single(predicate)Single(), Single(predicate)Returns Result with distinct Empty/Multiple errors.
FirstOrDefault / LastOrDefault / SingleOrDefaultNo direct equivalentUse the Option/Result outcome and an explicit fallback.
Aggregate without a seed, Sum, Average, Min, MaxNot yet implementedSeeded Fold can express basic accumulation.
OrderBy, ThenBy, GroupBy, Join, Distinct, Union, Intersect, Except, ZipNot yet implementedOrdering, equality and pairing contracts remain future work.

Behavior and limits.

The roles are comparable to .NET collection interfaces, with replacement separated from growth. More precise contracts help describe what an algorithm requires, at the cost of more interface distinctions. Filter and Map are lazy; ToList materializes, First/Last return Option and Single returns Result.

Detailed contract and comparisons →

PROPOSED DIRECTION

Where we’re heading.

Variance, immutable or frozen providers, and broader query coverage need separate decisions. The aim is useful collection contracts, not reproducing every .NET collection type. Iterator and mutation behavior should stay explicit.

Related proposals and open questions →

What would you try?

Bring a small use case, the code you tried and the behavior you expected. Which part of this contract helps, and which part should change?

Share feedback on GitHub ↗