Proposal: For-Yield Comprehensions
Status: Approved Author: Eric (with AI assistance) Created: 2026-01-30 Approved: 2026-01-30 Affects: Compiler, expressions, type inference
Summary
This proposal formalizes for...yield comprehension semantics, including type inference, filtering, nesting, and interaction with break/continue.
Problem Statement
The spec shows for...yield syntax but leaves unclear:
- Type inference: How is the result type determined?
- Filtering: How does
iffiltering work? - Nesting: How do nested comprehensions behave?
- Break/Continue: What do these mean in yield context?
- Empty results: What happens when nothing is yielded?
Syntax
Basic Form
for element in iterable yield expression
With Filter
for element in iterable if condition yield expression
Use && for multiple conditions:
for element in iterable if cond1 && cond2 yield expression
With Binding Pattern
for (key, value) in map yield expression
for { x, y } in points yield expression
Semantics
Desugaring
for...yield desugars to iterator methods:
// This:
for x in items yield x * 2
// Desugars to:
items.iter().map(transform: x -> x * 2).collect()
With filter:
// This:
for x in items if x > 0 yield x * 2
// Desugars to:
items.iter().filter(predicate: x -> x > 0).map(transform: x -> x * 2).collect()
Type Inference
The result type is inferred from context:
let numbers: [int] = for x in items yield x.id // -> [int]
let set: Set<str> = for x in items yield x.name // -> Set<str>
Default Collection Type
Without type context, for...yield collects into a list:
let result = for x in 0..5 yield x * 2 // type is [int]
This is consistent with the Iterator trait’s .collect() method defaulting to [T].
Collect Target
Any type implementing Collect<T> can be the target:
// Collect into list
let list: [int] = for x in items yield x
// Collect into set
let set: Set<int> = for x in items yield x
// Collect into map (yielding key-value tuples)
let map: {str: int} = for x in items yield (x.name, x.value)
Map Collection
Maps implement Collect<(K, V)>:
let by_id: {int: User} = for user in users yield (user.id, user)
If duplicate keys are yielded, later values overwrite earlier ones.
Filtering
Single Condition
for x in numbers if x > 0 yield x
Multiple Conditions
Chain conditions with &&:
for x in numbers if x > 0 && x < 100 yield x
Filter Position
Filter comes after the binding, before yield:
for x in items if predicate(x) yield transform(x)
// ^^^^^^^^^^^^^^^^ filter
// ^^^^^^^^^^^^^^^^ yield expression
Nested Comprehensions
Flat Nesting
Nested for clauses produce a flat result:
for x in xs for y in ys yield (x, y)
// Equivalent to:
// [(x, y) for each x in xs, for each y in ys]
With Filters
for x in xs if x > 0 for y in ys if y > 0 yield x * y
Desugaring
// This:
for x in xs for y in ys yield (x, y)
// Desugars to:
xs.iter().flat_map(transform: x -> ys.iter().map(transform: y -> (x, y))).collect()
Break and Continue
Continue Without Value
Skips the current element (does not add to collection):
for x in items yield
if skip(x) then continue, // Don't add anything
transform(x),
Equivalent to filtering:
for x in items if !skip(x) yield transform(x)
Continue With Value
Uses the value instead of the yield expression:
for x in items yield
if special(x) then continue x * 10, // Use this value
transform(x), // Otherwise use this
Break Without Value
Stops iteration, collects results so far:
for x in items yield
if done(x) then break, // Stop here
transform(x),
Break With Value
Stops iteration and adds a final value:
for x in items yield
if done(x) then break x, // Add x and stop
transform(x),
Empty Results
No Elements
If the source is empty, the result is empty:
for x in [] yield x * 2 // []
All Filtered
If all elements are filtered out:
for x in [1, 2, 3] if x > 10 yield x // []
Break Immediately
If break occurs before any yield:
for x in items yield
break,
x,
// []
Type Constraints
Iterable Source
The source must implement Iterable:
for x in items yield x // items must be Iterable
Collect Target
The result must be Collect<T> where T is the yield type:
let list: [int] = for x in items yield x.count // OK: [int]: Collect<int>
let bad: int = for x in items yield x.count // ERROR: int is not Collect<int>
Interaction with Patterns
In Run
{
let data = prepare()
let results = for x in data yield process(x)
summarize(results)
}
In Match Arms
match source {
Some(items) -> for x in items yield x * 2
None -> []
}
Performance
Lazy Evaluation
The desugared iterator chain is lazy:
for x in items if expensive_filter(x) yield transform(x)
// Only calls expensive_filter and transform as needed
Short-Circuit on Break
Break stops iteration immediately:
for x in large_list yield
if found(x) then break x,
x,
// Stops at first found, doesn't traverse entire list
Error Messages
Non-Iterable Source
error[E0890]: `for` requires `Iterable` source
--> src/main.ori:5:10
|
5 | for x in 42 yield x
| ^^ `int` does not implement `Iterable`
|
= help: use a range: `0..42`
Non-Collectible Target
error[E0891]: cannot collect into `int`
--> src/main.ori:5:1
|
5 | let n: int = for x in items yield x
| ^^^^^^^^^^^^^^^^^^^^^^ produces collection, not `int`
|
= note: `for...yield` produces a collection type
= help: use `.fold()` or `.count()` for a single value
Type Mismatch in Yield
error[E0892]: mismatched types in `yield`
--> src/main.ori:5:30
|
5 | let list: [int] = for x in items yield x.name
| ^^^^^^ expected `int`, found `str`
|
= note: expected element type `int` for `[int]`
Grammar Changes
Update grammar.ebnf § EXPRESSIONS to include:
for_yield_exprproduction with optionaliffilter and nestedforclausesbreakandcontinuewith optional value in yield context
Examples
Basic Transformation
let squares = for x in 0..10 yield x * x
// [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Filtering
let evens = for x in 0..10 if x % 2 == 0 yield x
// [0, 2, 4, 6, 8]
Nested
let pairs = for x in 0..3 for y in 0..3 yield (x, y)
// [(0,0), (0,1), (0,2), (1,0), (1,1), (1,2), (2,0), (2,1), (2,2)]
With Complex Logic
let processed = for item in items yield
if item.skip then continue,
if item.stop then break,
match item.transform() {
Ok(v) -> v
Err(_) -> continue
},
Into Set
let unique_names: Set<str> = for user in users yield user.name
Into Map
let by_id: {int: User} = for user in users yield (user.id, user)
Spec Changes Required
Update 10-patterns.md
Add For-Yield section covering:
- Desugaring semantics
- Type inference rules
- Filter syntax
- Nesting behavior
- Break/continue interaction
Update 09-expressions.md
Cross-reference to for-yield comprehensions.
Summary
| Aspect | Behavior |
|---|---|
| Basic syntax | for x in items yield expr |
| Filter syntax | for x in items if cond yield expr (single if with && for multiple) |
| Desugars to | .iter().map().collect() or .filter().map().collect() |
| Result type | Inferred from context or defaults to [T] |
| Map collection | {K: V} implements Collect<(K, V)>; duplicates overwrite |
| Empty source | Empty result |
| All filtered | Empty result |
| Continue | Skip element (no value) or substitute value |
| Break | Stop iteration, optionally add final value |
| Nesting | Flat result via flat_map |