# Lazy Sequences
`map`, `filter`, `take`, `drop`, and `mapcat` return **lazy sequences**
when given a collection argument. The work isn't done until you
consume the result. This lets pipelines over large or infinite inputs
stay efficient.
## What Is Lazy?
A lazy sequence computes its elements on demand. The chain below
doesn't walk the whole input; it only computes what the terminal
operation needs:
@{ 1 2 3 4 5 6 7 8 9 10 }
|> filter { ($it % 2) == 0 }
|> map { $it * 10 }
|> take 2
|> collect
# => @{ 20 40 }
# only the first two even numbers were mapped
The pipeline above terminates as soon as `take 2` has its two values.
If the source were infinite, the pipeline would still complete.
## Operations That Return Lazy Seqs
| Op | 1-arg (transducer) | 2-arg (lazy seq) |
|---|---|---|
| `map` | `[map f]` | `[map $xs f]` or `$xs \|> map f` |
| `filter` | `[filter pred?]` | `[filter $xs pred?]` or `$xs \|> filter pred?` |
| `take` | `[take n]` | `[take $xs n]` or `$xs \|> take n` |
| `drop` | `[drop n]` | `[drop $xs n]` or `$xs \|> drop n` |
| `mapcat` | `[mapcat f]` | `[mapcat $xs f]` or `$xs \|> mapcat f` |
The 1-arg form returns a **transducer** (see
[Transducers](transducers)).
## Realising Lazy Seqs
Lazy seqs are not directly printable. Use a terminal operation to
force them:
set xs @{ 1 2 3 4 5 } $xs |> map { $it * 2 } |> collect # force to list $xs |> filter even? |> count # count of matches $xs |> map { $it * 2 } |> reduce 0 { $acc + $it } # fold $xs |> filter { $it > 2 } |> find { $it > 3 } # first match $xs |> map { $it * 2 } |> join "," # stringify $xs |> filter { $it > 2 } |> first # same as `find` without pred $xs |> filter { $it > 2 } |> last
### TODO Implicit Realisation
`for` loops, `echo`, and assignment (`set`) auto-realise a lazy seq:
for x [map @{1 2 3} { $it * 2 }] { echo $x } # realises via for-loop echo $xs |> map { $it * 2 } # realises via echo set result $xs |> map { $it * 2 } # realises on assignment
## Pipelines vs Nested Calls
Pipelines are the idiomatic form for multi-step transforms:
# ✓ Preferred @{ 1 2 3 } |> map { $it * 2 } |> filter { $it > 2 } |> collect # ✗ Works, but unidiomatic [filter [map @{ 1 2 3 } { $it * 2 }] { $it > 2 }]
The pipeline form lets each step read left-to-right and avoids
rightward drift.
## Step Fusion
Pipeline steps fuse at runtime — the realisation walks the source
once and runs every transform per element. No intermediate
collections are allocated.
@{ 1 2 3 4 5 }
|> map { $it * 2 }
|> map { $it + 1 }
|> filter { $it > 4 }
|> collect
# one walk, two maps and a filter per element
## Caching
Once a lazy seq element is forced, it's cached. Re-walking the same
result doesn't re-run the pipeline:
set xs [map @{ 1 2 3 } { expensive-op $it }] echo [$xs first] # runs expensive-op on 1 echo [$xs first] # uses cached result
## Postfix Sugar
Pipeline operators are the most natural form, but single-step calls
can also use postfix on a variable:
set xs @{ 1 2 3 } set doubled $xs(map { $it * 2 }) set evens $xs(filter { ($it % 2) == 0 })
Note: there's **no space** between the variable and the opening paren.
`$xs (map ...)` is a syntax error.
## Iteration Over Maps and Strings
`ISeq` works on any sequence, not just lists. Maps yield `@{key value}`
pairs; strings yield single chars:
%{ :a 1 :b 2 } |> map { $it~1 } |> collect # => @{ 1 2 }
"hello" |> map { $it(upper) } |> join "" # => "HELLO"
For more on this, see [Collections](collections).
## Early Termination With `take`
`[take n]` is the primary tool for terminating an infinite or very
large pipeline:
def naturals [iterate { $it + 1 } 1] # if you have iterate # Without iterate, use range with a large bound and take what you need [range 0 1000000] |> filter { ($it % 7) == 0 } |> take 5 |> collect # => @{ 0 7 14 21 28 }
## When To Use Transducers Instead
The lazy-seq form is perfect for one-shot collection-to-collection
transforms. Reach for transducers (see [Transducers](transducers))
when:
- The same pipeline is applied to multiple collections.
- The sink is something other than a vector (set, map, sum, custom
reducer).
- You want explicit early termination across the whole chain via
`%Reduced`.
## Quick Reference
| Op | Returns | Notes |
|---|---|---|
| `map` | Lazy seq (2-arg) | Apply f to each element |
| `filter` | Lazy seq (2-arg) | Keep elements where pred truthy |
| `take` | Lazy seq (2-arg) | First n elements |
| `drop` | Lazy seq (2-arg) | All but first n elements |
| `mapcat` | Lazy seq (2-arg) | Map then concatenate results |
| `collect` | List | Force a lazy seq |
| `reduce` | Folded value | Reduce with init and step fn |
| `find` | First match or nil | Find first satisfying element |
| `any?` / `all?` | Boolean | Existence / universality |
| `group` | Map keyed by result | Partition by function |
| `count` | Number | Length after realisation |
| `first` / `last` | Element | Edges |
| `sort` | List | Sorted copy |
| `unique` | List | De-duplicated |