Generators and Lazy Sequences Guide

Turmeric provides two related features for lazy, incremental computation:

Generators

The gen form

A generator function uses gen in place of a function body and yield to produce values one at a time. The compiler rewrites each generator into a C struct plus a _next dispatch function -- no continuations, no heap alloc per step.

(defn integers-from [start :int] : (Generator :int)
  (gen []
    (let [^mut i start]
      (while true
        (yield i)
        (set! i (+ i 1))))))

(defn range-gen [lo :int hi :int] : (Generator :int)
  (gen []
    (let [^mut i lo]
      (while (< i hi)
        (yield i)
        (set! i (+ i 1))))))

Consuming a generator

Use gen-next to advance the generator. It returns a ptr<void> -- non-NULL when a value was yielded, NULL when exhausted. Use the gen.tur helpers:

(load "stdlib/gen.tur")

(let [g (range-gen 0 5)]
  (while (gen-some? (let [v (gen-next g)]
                      (when (gen-some? v)
                        (println (gen-unwrap v)))
                      v))))
;; gen-for-each macro -- preferred for side effects
(gen-for-each (range-gen 0 5)
              (fn [x] (println x)))
; prints 0 1 2 3 4

gen-collect -- materialise to array

(let [arr (gen-collect (range-gen 1 6))]
  (println (gen-arr-len arr))        ; 5
  (println (gen-arr-get arr 2)))     ; 3

gen-nth -- nth yielded value

(gen-nth (range-gen 10 20) 3)   ; => 13 (0-indexed)

yield* -- re-yield from an inner generator

(defn concat-gen [g1 g2] : (Generator :int)
  (gen []
    (yield* g1)
    (yield* g2)))

Supported control flow in gen bodies

Form Supported
while with yield inside Yes
if / cond with yield in branches Yes
let bindings that span a yield point Yes (promoted to struct field)
yield inside match arms No (v1 limitation)
Recursive generators No (v1 limitation)

Generated C

The compiler emits a struct per gen body and a _next function with a switch on the state tag. Each yield saves state and returns. This means:

  • Zero dynamic allocation per next call
  • Zero function-pointer indirection
  • Inlineable by the C compiler in performance-critical paths

Lazy Sequences (Seq)

The Seq type wraps a generator-producing thunk. Combinators chain generators without building intermediate collections.

(import seq/core     :refer [seq-from-vec seq-of empty-seq])
(import seq/builders :refer [seq/range seq/repeat])
(import seq/transform :refer [seq/map seq/filter seq/take seq/drop])
(import seq/combine  :refer [seq/zip seq/concat])
(import seq/consume  :refer [seq/into-vec seq/foldl seq/for-each seq/first])

Creating sequences

;; From a range [lo, hi)
(seq/range 0 10)

;; From a range with step
(seq/range-step 0 20 2)      ; 0 2 4 6 8 10 12 14 16 18

;; Wrap a vec
(seq-from-vec [1 2 3 4 5])

;; Single-element sequence
(seq-of 42)

;; Infinite repetition
(seq/repeat "hello")

;; Infinite thunk calls
(seq/repeatedly (fn [] (rand-int 100)))

;; x, f(x), f(f(x)), ...
(seq/iterate 1 (fn [x] (* x 2)))    ; 1 2 4 8 16 ...

;; Cycle a finite sequence infinitely
(seq/cycle (seq/range 0 3))          ; 0 1 2 0 1 2 0 1 2 ...

Transformations

All transformations are lazy -- no work is done until a consumer drives the sequence:

;; map
(seq/map (fn [x] (* x x)) (seq/range 1 6))
; lazy: 1 4 9 16 25

;; filter
(seq/filter even? (seq/range 0 10))
; lazy: 0 2 4 6 8

;; take / drop
(seq/take 3 (seq/repeat 7))     ; lazy: 7 7 7
(seq/drop 2 (seq/range 0 5))    ; lazy: 2 3 4

;; take-while / drop-while
(seq/take-while (fn [x] (< x 5)) (seq/range 0 100))
; lazy: 0 1 2 3 4

;; map-indexed: receive (index value) pairs
(seq/map-indexed (fn [i x] (pair i x)) (seq/range 10 13))
; lazy: (0 10) (1 11) (2 12)

;; filter-map: map + filter in one pass (None values dropped)
(seq/filter-map (fn [x] (if (even? x) (some (* x 10)) (none)))
                (seq/range 0 5))
; lazy: 0 20 40

;; flat-map
(seq/flat-map (fn [x] (seq/range 0 x)) (seq/range 1 4))
; lazy: 0   0 1   0 1 2

;; flatten
(seq/flatten (seq-from-vec [(seq/range 0 2) (seq/range 5 7)]))
; lazy: 0 1 5 6

Combinators

;; concat two sequences
(seq/concat (seq/range 0 3) (seq/range 10 13))
; lazy: 0 1 2 10 11 12

;; zip two sequences (stops at shorter)
(seq/zip (seq/range 0 3) (seq/range 10 13))
; lazy: (0 10) (1 11) (2 12)

;; zip-with a combining function
(seq/zip-with + (seq/range 0 3) (seq/range 10 13))
; lazy: 10 12 14

;; interleave: alternate elements
(seq/interleave (seq/range 0 3) (seq/range 10 13))
; lazy: 0 10 1 11 2 12

Consumers (force evaluation)

;; collect to vec
(seq/into-vec (seq/range 0 5))          ; => [0 1 2 3 4]

;; collect to cons list
(seq/into-list (seq/range 0 3))         ; => (0 1 2)

;; nth element (0-indexed)
(seq/nth 2 (seq/range 10 20))           ; => (some 12)

;; first element
(seq/first (seq/filter even? (seq/range 1 10)))  ; => (some 2)

;; count elements
(seq/count (seq/range 0 100))           ; => 100

;; foldl: left fold with accumulator
(seq/foldl 0 + (seq/range 1 6))        ; => 15

;; reduce: fold with first element as seed
(seq/reduce + (seq/range 1 6))         ; => (some 15)

;; for-each: side effects
(seq/for-each println (seq/range 0 3)) ; prints 0 1 2

;; short-circuit consumers
(seq/any? even? (seq/range 1 10))      ; => true (stops at 2)
(seq/all? even? (seq/range 0 6 2))    ; => true  (checks 0 2 4)
(seq/find even? (seq/range 1 10))     ; => (some 2)
(seq/find-index even? (seq/range 1 10)) ; => (some 1)

Chaining with ->> (pipeline style)

(->> (seq/range 0 1000)
     (seq/filter even?)
     (seq/map (fn [x] (* x x)))
     (seq/take-while (fn [x] (< x 10000)))
     (seq/foldl 0 +))
; => 0 + 4 + 16 + 36 + ... + 9604

No intermediate vecs are allocated; the chain compiles to nested state-machine dispatch.


Range Types

Range is a first-class value type representing a continuous interval with inclusive, exclusive, or unbounded endpoints. It is not itself a sequence, but integer ranges can be converted to Seq.

(import range :refer [closed-range open-range closed-open-range
                      range-contains? range-span seq/from-range])

Constructors

Function Interval
(closed-range lo hi) [lo, hi] -- both inclusive
(open-range lo hi) (lo, hi) -- both exclusive
(closed-open-range lo hi) [lo, hi) -- inclusive lower, exclusive upper
(open-closed-range lo hi) (lo, hi] -- exclusive lower, inclusive upper
(at-least-range lo) [lo, +inf)
(greater-than-range lo) (lo, +inf)
(at-most-range hi) (-inf, hi]
(less-than-range hi) (-inf, hi)
(singleton-range v) [v, v]
(unbounded-range) (-inf, +inf)

Predicates

(range-contains? (closed-range 1 10) 5)     ; => true
(range-contains? (open-range   1 10) 10)    ; => false
(range-encloses? (closed-range 0 10) (closed-range 2 8))  ; => true
(range-overlaps? (closed-range 0 5)  (closed-range 3 8))  ; => true
(range-connected? (closed-open-range 0 5) (closed-range 5 10)) ; => true
(empty-range? (open-range 5 5))             ; => true
(singleton-range? (closed-range 7 7))       ; => true
(bounded-range? (at-least-range 0))         ; => false

Set operations

;; Convex hull of two ranges
(range-span (closed-range 0 3) (closed-range 7 10))
; => [0, 10]

;; Gap between non-overlapping ranges
(range-gap (closed-open-range 0 5) (closed-range 7 10))
; => [5, 7)

;; Intersection of overlapping ranges
(range-intersection (closed-range 0 8) (closed-range 5 10))
; => [5, 8]

Converting a Range to a Seq

For discrete (integer) ranges, use seq/from-range or seq/from-range-step:

;; [1, 5] => (Seq 1 2 3 4 5)
(seq/into-vec (seq/from-range (closed-range 1 5)))
; => [1 2 3 4 5]

;; [0, 10) with step 2 => (Seq 0 2 4 6 8)
(seq/into-vec (seq/from-range-step 2 (closed-open-range 0 10)))
; => [0 2 4 6 8]

stdlib/gen.tur API Reference

Symbol Description
gen-some? True if gen-next returned a value (non-NULL)
gen-unwrap Extract the :int value from a non-NULL gen-next result
gen-none Return NULL to represent no value
gen-arr-new Allocate an empty growable array
gen-arr-push! Append a value to a gen array
gen-arr-len Number of elements in a gen array
gen-arr-get Element at index (0-based)
gen-collect Macro: drive a generator, collect all values into a gen array
gen-for-each Macro: drive a generator, call f on each value
gen-nth Macro: return the nth value from a generator, or gen-none
yield* Macro: re-yield every value from an inner generator

stdlib/seq/ Module Reference

Module Exported symbols
seq/core seq-iter, seq-of, empty-seq, seq-from-vec, seq-from-list
seq/builders seq/range, seq/range-step, seq/repeat, seq/repeatedly, seq/cycle, seq/iterate, seq/unfold
seq/transform seq/map, seq/filter, seq/take, seq/drop, seq/take-while, seq/drop-while, seq/map-indexed, seq/filter-map, seq/flat-map, seq/flatten
seq/combine seq/concat, seq/chain, seq/zip, seq/zip-with, seq/interleave
seq/consume seq/into-vec, seq/into-list, seq/nth, seq/first, seq/last, seq/count, seq/reduce, seq/foldl, seq/for-each, seq/any?, seq/all?, seq/find, seq/find-index

stdlib/range.tur API Reference

Function Description
closed-range / open-range / closed-open-range / open-closed-range Bounded constructors
at-least-range / greater-than-range Unbounded above
at-most-range / less-than-range Unbounded below
singleton-range / unbounded-range Single value / no bounds
range-contains? Test membership
range-encloses? Test subset
range-overlaps? Test nonempty intersection
range-connected? Test adjacency (touching counts)
bounded-range? / bounded-above? / bounded-below? Boundedness tests
unbounded-above? / unbounded-below? Unboundedness tests
empty-range? / nonempty-range? / singleton-range? Cardinality
range-span Convex hull of two ranges
range-gap Interval between non-overlapping ranges
range-intersection Largest range within both
seq/from-range Integer range to Seq (step 1)
seq/from-range-step Integer range to Seq with custom step