Skip to content

Latest commit

 

History

History
1118 lines (969 loc) · 30.3 KB

lazy.org

File metadata and controls

1118 lines (969 loc) · 30.3 KB

lazy

-*-org-mode-*-

about

The `fun` module is an excellent source of eager functional tools. However, it cannot be used to represent infinite ‘sequences’. This module provides them.

There are three levels to this.

  • iterator
  • generators
  • high-level iterators
  • consumers

Typically you start by providing input to a generator, then pipe them into any number of iterators, and finally pipe that to a consumer.

use ./test
use ./base
use ./fun

iterator structure

Basic iterator structure and predicate.

Iterators have four zero-arity functions:

  • init: performs any initialization steps.
  • step: advances iteration to the next value.
  • curr: outputs the next value.
  • done: returns a boolean. `true` if the iterator has been exhausted.
fn make-iterator {|&init={ } &curr={ } &step={ } &done={ }|
  put [&init=$init
    &curr=$curr
    &step=$step
    &done=$done]
}

fn inf-iterator {|&init={ } &curr={ } &step={ }|
  make-iterator &init=$init &curr=$curr &step=$step &done={ put $false }
}

fn nest-iterator {|iter &init={ } &curr={ } &step={ } &done={ } &exhaust={ }|
  make-iterator ^
  &init={
    nop ($iter[init])
    nop ($init)
  } ^
  &curr=$curr &step=$step ^
  &done={ or ($iter[done]) ($done) }
}

fn is-iterator {|x|
  and (eq (kind-of $x) map) ^
      (has-key $x init) ^
      (has-key $x step) ^
      (has-key $x curr) ^
      (has-key $x done) ^
      (eq (kind-of $x[init]) fn) ^
      (eq (kind-of $x[step]) fn) ^
      (eq (kind-of $x[curr]) fn) ^
      (eq (kind-of $x[done]) fn)
}

generators

fn to-iter {|@arr|
  set @arr = (base:check-pipe $arr)
  var i c

  make-iterator ^
  &init={ set i c = 0 (count $arr) } ^
  &curr={ put $arr[$i] } ^
  &step={ set i = (base:inc $i) } ^
  &done={ >= $i $c }
}

fn cycle {|@arr|
  set @arr = (base:check-pipe $arr)
  var i c

  inf-iterator ^
  &init={ set i c = 0 (count $arr) } ^
  &curr={ put $arr[(% $i $c)] } ^
  &step={ set i = (base:inc $i) }
}

fn iterate {|f seed|
  var x

  inf-iterator ^
  &init={ set x = $seed } ^
  &curr={ put $x } ^
  &step={ set x = ($f $x) }
}

fn nums {|&start=(num 0) &stop=$nil &step=(num 1)|
  var x

  make-iterator ^
  &init={ set x = (num $start) } ^
  &curr={ put $x } ^
  &step={ set x = (+ $x $step) } ^
  &done=(if (eq $stop $nil) {
      put { put $false }
    } elif (> $step 0) {
      put { >= $x $stop }
    } elif (< $step 0) {
      put { <= $x $stop }
  })
}

fn repeatedly {|f|
  inf-iterator &curr={ put ($f) }
}

fn repeat {|@xs|
  inf-iterator &curr={ put $@xs }
}

get-iter

Reads the iterator from the pipe, if missing from the input.

fn get-iter {|@iter|
  if (base:is-one (count $iter)) {
    put $@iter
  } else {
    one
  }
}

complex iterators

fn prepend {|list @iter|
  # Cleverly avoids conditionals in the `step` function after it's exhausted
  # the `list`

  set iter = (get-iter $@iter)

  # intermediate vars
  var i c get step

  # static vars
  var getiter = { put ($iter[curr]) }
  var getlist = { put $list[$i]}

  var stepiter steplist
  set stepiter = {
    nop ($iter[step])
    put $stepiter
    put $getiter
  }
  set steplist = {
    set i = (base:inc $i)
    if (== $i $c) {
      put $stepiter
      put $getiter
    } else {
      put $steplist
      put $getlist
    }
  }

  # iterator
  nest-iterator $iter ^
  &init={ set i c get step = 0 (count $list) $getlist $steplist } ^
  &curr={ put ($get) } ^
  &step={ set step get = ($step) }
}

fn keep {|f @iter|
  set iter = (get-iter $@iter)
  var x

  var next = {
    while (not ($iter[done])) {
      var @xs = ($f ($iter[curr]))
      nop ($iter[step])
      if (and (not-eq $xs []) (not-eq $xs [$nil])) {
        put $@xs
        break
      }
    }
  }

  nest-iterator $iter ^
  &init={ set @x = ($next) } ^
  &curr={ put $@x } ^
  &step={ set @x = ($next) }
}

fn filter {|f @iter|
  set iter = (get-iter $@iter)
  var x

  var next = {
    while (not ($iter[done])) {
      var @curr = ($iter[curr])
      var @res = ($f $@curr)
      nop ($iter[step])
      if (not-eq $res []) {
        if $@res {
          put $@curr
          break
        }
      }
    }
  }

  nest-iterator $iter ^
  &init={ set @x = ($next) } ^
  &curr={ put $@x } ^
  &step={ set @x = ($next) }
}

fn interleave {|@iters|
  set @iters = (base:check-pipe $iters)
  use builtin
  var xs

  var next = {
    if (fun:not-any {|i| put ($i[done]) } $@iters) {
      builtin:each {|i| put ($i[curr])} $iters
    }
  }

  make-iterator ^
  &init={
    for i $iters { nop ($i[init]) }
    set @xs = ($next)
  } ^
  &curr={ put $xs[0] } ^
  &step={
    set xs = (base:rest $xs)
    if (base:is-empty $xs) {
      builtin:each {|i| nop ($i[step])} $iters
      set @xs = ($next)
    }
  } ^
  &done={ and (base:is-empty $xs) (fun:some {|i| put ($i[done]) } $@iters) }
}

fn unique {|@iter &count=$false &cmp=$eq~|
  set iter = (get-iter $@iter)
  if $count {
    var prev-el
    var el

    var next = {
      if ($iter[done]) {
        put $nil
      } else {
        var i = 0
        var curr = ($iter[curr])
        while (and (not ($iter[done])) ($cmp $curr ($iter[curr]))) {
          nop ($iter[step])
          set i = (base:inc $i)
        }
        put [$curr $i]
      }
    }

    make-iterator ^
    &init={
      nop ($iter[init])
      set prev-el = ($next)
      set el = ($next)
    } ^
    &curr={ put $prev-el } ^
    &step={
      set prev-el = $el
      set el = ($next)
    } ^
    &done={ eq $prev-el $nil }
  } else {
    nest-iterator $iter ^
    &curr={ put ($iter[curr]) } ^
    &step={
      var curr = ($iter[curr])
      while (and (not ($iter[done])) ($cmp $curr ($iter[curr]))) {
        nop ($iter[step])
      }
    }
  }
}

fn partition {|n @iter &step=$nil &pad=$nil|
  set iter = (get-iter $@iter)
  set step = (or $step $n)
  use builtin
  var buffer done

  var read = {|i|
    while (and (not ($iter[done])) (> $i 0)) {
      put ($iter[curr])
      nop ($iter[step])
      set i = (base:dec $i)
    }
    put $i
  }

  var next = (
    if (eq $pad $nil) {
      if (>= $step $n) {
        put {|_|
          var @xs _ = ($read $step)
          set @xs = (builtin:take $n $xs)
          if (< (count $xs) $n) {
            put $nil $true
          } else {
            put $xs $false
          }
        }
      } else {
        put {|buffer|
          var @xs = (drop $step $buffer | take $n)
          var @xs2 i = ($read (- $n (count $xs)))
          if (> $i 0) {
            put $nil $true
          } else {
            put (base:concat2 $xs $xs2) $false
          }
        }
      }
    } else {
      if (>= $step $n) {
        put {|_|
          var @xs i = ($read $step)
          set @xs = (builtin:take $n $xs)
          set i = (- $n (count $xs))
          put (base:concat2 $xs [(builtin:take $i $pad)]) (> $i 0)
        }
      } else {
        put {|buffer|
          var @xs = (drop $step $buffer | take $n)
          var @xs2 i = ($read (- $n (count $xs)))
          put (base:concat2 $xs $xs2 [(builtin:take $i $pad)]) (> $i 0)
        }
      }
  })

  var next-if = (
    if (>= $step $n) {
      put {|buffer done|
        if ($iter[done]) {
          put $nil $true
        } else {
          $next $buffer
        }
      }
    } else {
      put {|buffer done|
        if $done {
          put $nil $true
        } else {
          $next $buffer
        }
      }
    })

  make-iterator ^
  &init={
    nop ($iter[init])
    set buffer done = ($next-if [] $false)
  } ^
  &curr={ put $buffer } ^
  &step={ set buffer done = ($next-if $buffer $done) } ^
  &done={ eq $buffer $nil }
}

fn take-nth {|n @iter|
  set iter = (get-iter $@iter)
  var x

  var next = {
    var i = $n
    while (and (not ($iter[done])) (> $i 0)) {
      nop ($iter[step])
      set i = (base:dec $i)
    }

    if (== $i (num 0)) {
      put ($iter[curr])
    }
  }

  nest-iterator $iter ^
  &init={ set @x = ($iter[curr]) } ^
  &curr={ put $@x } ^
  &step={ set @x = ($next) }
}

fn drop-last {|n @iter|
  set iter = (get-iter $@iter)
  var buffer

  nest-iterator $iter ^
  &init={
    set buffer = []
    var i = $n
    while (and (not ($iter[done])) (> $i 0)) {
      set buffer = (base:append $buffer ($iter[curr]))
      nop ($iter[step])
      set i = (base:dec $i)
    }
  } ^
  &curr={
    put $buffer[0]
    set buffer = (base:append $buffer ($iter[curr]))
  } ^
  &step={
    set buffer = (base:rest $buffer)
    nop ($iter[step])
  }
}

fn keep-indexed {|f @iter &pred=(fun:complement $base:is-nil~)|
  set iter = (get-iter $@iter)
  var i x

  var next = {
    while (not ($iter[done])) {
      var @curr = ($iter[curr])
      var @res = ($f $i $@curr)
      nop ($iter[step])
      set i = (base:inc $i)
      if (not-eq $res []) {
        if ($pred $@res) {
          put $@res
          break
        }
      }
    }
  }

  nest-iterator $iter ^
  &init={
    set i = 0
    set @x = ($next)
  } ^
  &curr={ put $@x } ^
  &step={ set @x = ($next) }
}

simple iterators

Relatively simple iterators

fn remove {|f @iter|
  filter (fun:complement $f) (get-iter $@iter)
}

fn take {|n @iter|
  set iter = (get-iter $@iter)
  var i

  nest-iterator $iter ^
  &init={ set i = (num 0) } ^
  &curr={ put ($iter[curr]) } ^
  &step={
    set i = (base:inc $i)
    nop ($iter[step])
  } ^
  &done={ >= $i $n }
}

fn take-while {|f @iter|
  set iter = (get-iter $@iter)

  nest-iterator $iter ^
  &curr={ put ($iter[curr]) } ^
  &step={ nop ($iter[step]) } ^
  &done={ eq ($f ($iter[curr])) $false }
}

fn drop {|n @iter|
  set iter = (get-iter $@iter)
  var i

  nest-iterator $iter ^
  &init={
    set i = $n
    while (and (not ($iter[done])) (> $i 0)) {
      nop ($iter[step])
      set i = (base:dec $i)
    }
  } ^
  &curr={ put ($iter[curr]) } ^
  &step={ nop ($iter[step]) } ^
  &done={ > $i 0 }
}

fn drop-while {|f @iter|
  set iter = (get-iter $@iter)

  nest-iterator $iter ^
  &init={
    while (and (not ($iter[done])) (eq ($f ($iter[curr])) $true)) {
      nop ($iter[step])
    }
  } ^
  &curr={ put ($iter[curr]) } ^
  &step={ nop ($iter[step]) }
}

fn butlast {|@iter|
  set iter = (get-iter $@iter)
  var x

  nest-iterator $iter ^
  &init={
    set x = ($iter[curr])
    nop ($iter[step])
  } ^
  &curr={
    put $x
    set x = ($iter[curr])
  } ^
  &step={ nop ($iter[step]) }
}

fn rest {|@iter|
  drop 1 (get-iter $@iter)
}

fn reductions {|f acc @iter|
  set iter = (get-iter $@iter)
  var start = $acc

  nest-iterator $iter ^
  &init={ set acc = $start } ^
  &curr={ put $acc } ^
  &step={
    set acc = ($f $acc ($iter[curr]))
    nop ($iter[step])
  }
}

fn each {|f @iter|
  set iter = (get-iter $@iter)
  nest-iterator $iter ^
  &curr={ $f ($iter[curr]) } ^
  &step={ nop ($iter[step]) }
}

fn map {|f @iters|
  set @iters = (base:check-pipe $iters)
  make-iterator ^
  &init={ for i $iters { nop ($i[init]) } } ^
  &curr={ $f (for i $iters { put ($i[curr]) }) } ^
  &step={ for i $iters { nop ($i[step]) } } ^
  &done={ fun:some {|i| put ($i[done]) } $@iters } ^
}

fn map-indexed {|f @iter|
  map $f (nums) (get-iter $@iter)
}

fn interpose {|sep @iter|
  set iter = (get-iter $@iter)

  var i
  var sep = (repeat $sep)
  var m = [&(num -1)=$sep &(num 1)=$iter]

  nest-iterator $iter ^
  &init={
    set i = (num 1)
    nop ($sep[init])
  } ^
  &curr={ put ($m[$i][curr]) } ^
  &step={
    nop ($m[$i][step])
    set i = (* $i -1)
  }
}

fn partition-all {|n @iter|
  partition $n (get-iter $@iter) &pad=[]
}

consumers

fn blast {|@iter|
  set iter = (get-iter $@iter)
  nop ($iter[init])
  while (not ($iter[done])) {
    put ($iter[curr])
    nop ($iter[step])
  }
}

fn first {|@iter|
  set iter = (get-iter $@iter)
  nop ($iter[init])
  if (not ($iter[done])) {
    put ($iter[curr])
  }
}

fn second {|@iter|
  rest (get-iter $@iter) | first
}

fn nth {|n @iter|
  drop  (base:dec $n) (get-iter $@iter) | first
}

fn some {|f @iter|
  keep $f (get-iter $@iter) | first
}

fn first-pred {|f @iter|
  filter $f (get-iter $@iter) | first
}

fn every {|f @iter|
  var @res = (first-pred (fun:complement $f) (get-iter $@iter))
  eq $res []
}

assertions

fn assert-iterator {
  |&fixtures=[&] &store=[&]|
  test:assert iterator {|@reality|
    and (== (count $reality) 1) ^
        (is-iterator $@reality)
  } &name=is-iterator &fixtures=$fixtures &store=$store
}

tests

var tests = [lazy.elv
  'This module allows you to express infinite sequences.  Typically you start by providing input to a generator, then pipe them into any number of iterators, and finally pipe that to a consumer.'
  '# Iterator structure'
  [make-iterator
   'Iterators have five zero-arity functions:'
   '- init: performs any initialization steps.'
   '- step: advances iteration to the next value.'
   '- curr: outputs the next value.'
   '- done: returns a boolean.  `true` if the iterator has been exhusted'
   '`inf-iterator` & `nest-iterator` are convenience wrappers around `make-iterator`.'
   (test:assert-map)
   { make-iterator }
   { make-iterator &init={ } &curr={ } &step={ } &done={ } }]

  [is-iterator
   'Simple predicate for iterators.  Runs `done` to be sure it returns a bool.'
   'All of the iterators satisfy this predicate.'
   (assert-iterator)
   { range 10 | to-iter }
   { cycle a b c }
   { iterate $base:inc~ (num 0) }
   { nums }
   { repeatedly { randint 100 } }
   { repeat (randint 100) }
   { to-iter d e f | prepend [a b c] }
   { range 10 | to-iter | take 5 }
   { cycle a b c | reductions $base:append~ [] }
   { use str; nums &start=(num 65) | each $str:from-codepoints~ }
   { nums | keep {|n| if (base:is-even $n) { put $n }} }
   { nums | filter $base:is-even~ }
   { nums | remove $base:is-even~ }
   { map $'+~' (to-iter (range 10)) (to-iter (range 10)) }
   { nums &start=10 &step=10 | map-indexed $'*~' }
   { range 10 | to-iter | drop 5 }
   { interleave (to-iter a b c) (to-iter 1 2 3) }
   { interpose , (range 10 | to-iter ) }
   { unique (to-iter a b b c c c a a a a d) }
   { unique (to-iter a b b c c c a a a a d) &count=$true }
   { nums | take-while {|n| < $n 5} }
   { nums | drop-while {|n| < $n 5} }
   { nums &stop=12 | partition 3 }
   { nums &stop=13 | partition-all 3 }
   { nums &stop=50 | take-nth 5 }
   { nums &stop=10 | drop-last 5 }
   { nums &stop=5 | butlast }
   { to-iter a b c d e f g | keep-indexed {|i x| put [$i $x]} &pred=(fun:comp $base:first~ $base:is-odd~) }]

  [init
   'The init function means that iterators should "start over" from the beginning.'
   (test:assert-one $true)
   {
     var iter = (range 10 | to-iter)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (cycle a b c)
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (iterate $base:inc~ (num 0))
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums)
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (repeatedly { put x })
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (repeat (randint 100))
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (to-iter d e f | prepend [a b c])
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (range 10 | to-iter | take 5)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (cycle a b c | reductions $base:append~ [])
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     use str
     var iter = (nums &start=(num 65) | each $str:from-codepoints~)
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums | keep {|n| if (base:is-even $n) { put $n }})
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums | filter $base:is-even~)
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums | remove $base:is-even~)
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (map $'+~' (to-iter (range 10)) (to-iter (range 10)))
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (nums &start=10 &step=10 | map-indexed $'*~')
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (range 10 | to-iter | drop 5)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (interleave (to-iter a b c) (to-iter 1 2 3))
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (interpose , (range 10 | to-iter ))
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (unique (to-iter a b b c c c a a a a d))
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (unique (to-iter a b b c c c a a a a d) &count=$true)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (nums | take-while {|n| < $n 5})
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums | drop-while {|n| < $n 5})
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums &stop=12 | partition 3)
     eq (take 10 $iter | blast | fun:listify) (take 10 $iter | blast | fun:listify)
   }
   {
     var iter = (nums &stop=13 | partition-all 3)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (nums &stop=50 | take-nth 5)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (nums &stop=10 | drop-last 5)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var iter = (nums &stop=5 | butlast)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }
   {
     var pred = (fun:comp $base:first~ $base:is-odd~)
     var iter = (to-iter a b c d e f g | keep-indexed {|i x| put [$i $x]} &pred=$pred)
     eq (blast $iter | fun:listify) (blast $iter | fun:listify)
   }]

  '# Generators'
  [to-iter
   'Simplest generator.  Transforms an "array" to an iterator.'
   (test:assert-each (range 10))
   { to-iter (range 10) | blast }
   { range 10 | to-iter | blast }]

  [cycle
   'cycles an "array" infinitely.'
   (test:assert-each a b c a b c a b c a)
   { cycle a b c | take 10 | blast }
   { put a b c | cycle | take 10 | blast }]

  [iterate
   'Returns an "array" of n, f(n), f(f(n)), etc.'
   (test:assert-each (range 10))
   { iterate $base:inc~ (num 0) | take 10 | blast }]

  [nums
   'With no options, starts counting up from 0.'
   (test:assert-each (range 10))
   { nums | take 10 | blast }
   'You can tell it to start at a specific value.'
   (test:assert-each (range 10 20))
   { nums &start=10 | take 10 | blast }
   'You can specify a step value.'
   (test:assert-each (num 0) (num 2) (num 4) (num 6) (num 8))
   { nums &step=2 | take 5 | blast }
   'It can be negative.'
   (test:assert-each (range 0 -10))
   { nums &step=-1 | take 10 | blast }
   'Stop values can also be provided, although they offer little value over `range`.'
   (test:assert-each (range 10))
   { nums &stop=10 | blast }
   '`nums` returns nothing if the inputs make no sense.'
   (test:assert-nothing)
   { nums &step=-1 &stop=10 | blast }]

  [repeatedly
   'Takes a zero-arity function and calls it infinitely.'
   (test:assert-count 5)
   { repeatedly { randint 100 } | take 5 | blast }]

  [repeat
   'Returns `x` infinitely'
   (test:assert-each x x x x x)
   { repeat x | take 5 | blast }]

  '# High-level iterators'
  [prepend
   'Prepends a list to an iterator'
   (test:assert-each a b c d e f)
   { to-iter d e f | prepend [a b c] | blast }]

  [take
   'Like `builtin:take` but for iterators.'
   (test:assert-each a b c a b c a b c a)
   { cycle a b c | take 10 | blast }
   { put a b c | cycle | take 10 | blast }
   'Exceeding the length of a nested iterator is handled gracefully.'
   (test:assert-each (num 0) (num 1) (num 2) (num 3) (num 4))
   { range 5 | to-iter | take 20 | blast }]

  [drop
   'Like `builtin:drop` but for iterators.'
   (test:assert-each (num 5) (num 6) (num 7) (num 8) (num 9))
   { range 10 | to-iter | drop 5 | blast }
   'Dropping more than the nested iterator is handled gracefully.'
   (test:assert-nothing)
   { range 10 | to-iter | drop 20 | blast }]

  [rest
   'Drops the first element from the iterator.'
   (test:assert-each (num 6) (num 7) (num 8) (num 9))
   { range 10 | to-iter | drop 5 | rest | blast }]

  [reductions
   'Like fun:reductions, but works with iterators.'
   (test:assert-each [] [a] [a b] [a b c] [a b c a])
   { cycle a b c | reductions $base:append~ [] | take 5 | blast }]

  [each
   'Like `builtin:each, but works with iterators`.'
   (test:assert-each A B C)
   { use str; nums &start=(num 65) | each $str:from-codepoints~ | take 3 | blast }]

  [map
   'Like `each`, but works with multiple iterators.'
   (test:assert-each (num 0) (num 2) (num 4) (num 6) (num 8))
   { map $'+~' (to-iter (range 10)) (to-iter (range 10)) | take 5 | blast }
   'Can work like `each`, but you should avoid this because it is less performant.'
   (test:assert-each A B C)
   { use str; nums &start=(num 65) | map $str:from-codepoints~ | take 3 | blast }]

  [map-indexed
   'Returns a sequence of `(f index element)`.'
   (test:assert-each (num 0) (num 20) (num 60) (num 120) (num 200))
   { nums &start=10 &step=10 | map-indexed $'*~' | take 5 | blast }]

  [keep
   "Returns result of `(f x)` when it's non-nil & non-empty."
   'Notice how these two results are different depending on where you place the `take`.'
   (test:assert-each (num 0) (num 2) (num 4) (num 6) (num 8))
   { nums | take 10 | keep {|n| if (base:is-even $n) { put $n }} | blast }
   (test:assert-each (num 0) (num 2) (num 4) (num 6) (num 8) (num 10) (num 12) (num 14) (num 16) (num 18))
   { nums | keep {|n| if (base:is-even $n) { put $n }} | take 10 | blast }]

  [filter
   "Returns `x` when `(f x)` is non-empty & truthy."
   (test:assert-each (num 0) (num 2) (num 4) (num 6) (num 8))
   { nums | filter $base:is-even~ | take 5 | blast }]

  [remove
   "Returns `x` when `(complement (f x))` is non-empty & truthy."
   (test:assert-each (num 1) (num 3) (num 5) (num 7) (num 9))
   { nums | remove $base:is-even~ | take 5 | blast }]

  [interleave
   'Returns a sequence of the first item in each iterator, then the second, etc.'
   (test:assert-each a 1 b 2 c 3)
   { interleave (to-iter a b c) (to-iter 1 2 3) | blast }
   'Understands when to stop short.'
   (test:assert-each a 1 b 2)
   { interleave (to-iter a b) (to-iter 1 2 3) | blast }
   { interleave (to-iter a b c) (to-iter 1 2) | blast }]

  [interpose
   'Returns the elements from the nested iterator, interposed with `sep`.'
   (test:assert-each a , b , c)
   { interpose , (to-iter a b c) | blast }
   'Needs to elements from iter in order to interpose sep.'
   (test:assert-each a)
   { interpose , (to-iter a) | blast }]

  [unique
   'Like `uniq` but for iterators.'
   (test:assert-each a b c a)
   { unique (to-iter a b b c c c a a a a) | blast }
   (test:assert-each a b c a d)
   { unique (to-iter a b b c c c a a a a d) | blast }
   (test:assert-each [a (num 1)] [b (num 2)] [c (num 3)] [a (num 4)])
   { unique (to-iter a b b c c c a a a a) &count=$true | blast }
   (test:assert-each [a (num 1)] [b (num 2)] [c (num 3)] [a (num 4)] [d (num 1)])
   { unique (to-iter a b b c c c a a a a d) &count=$true | blast }
   'Corner-case test'
   (test:assert-each a)
   { unique (to-iter a) | blast }
   (test:assert-each [a (num 1)])
   { unique (to-iter a) &count=$true | blast }]

  [take-while
   'Returns elements so long as `(f x)` returns $true.'
   (test:assert-each (num 0) (num 1) (num 2) (num 3) (num 4))
   { nums | take-while {|n| < $n 5} | blast}]

  [drop-while
   'Drops elements until `(f x)` returns false.'
   (test:assert-each (num 5) (num 6) (num 7) (num 8) (num 9))
   { nums | drop-while {|n| < $n 5} | take 5 | blast }]

  [partition
   "partitions an iterator into lists of size n."
   (test:assert-each [(num 0) (num 1) (num 2)] ^
                 [(num 3) (num 4) (num 5)] ^
                 [(num 6) (num 7) (num 8)] ^
                 [(num 9) (num 10) (num 11)])
   { nums &stop=12 | partition 3 | blast }

   "Drops items which don't complete the specified list size."
   { nums &stop=14 | partition 3 | blast }

   'Specify `&step=n` to specify a "starting point" for each partition.'
   (test:assert-each [(num 0) (num 1) (num 2)] [(num 5) (num 6) (num 7)])
   { nums &stop=12 | partition 3 &step=5 | blast }

   "`&step` can be < than the partition size."
   (test:assert-each [(num 0) (num 1)] [(num 1) (num 2)] [(num 2) (num 3)])
   { nums &stop=4 | partition 2 &step=1 | blast }

   "When there are not enough items to fill the last partition, a pad can be supplied."
   (test:assert-each [(num 0) (num 1) (num 2)] ^
                 [(num 3) (num 4) (num 5)] ^
                 [(num 6) (num 7) (num 8)] ^
                 [(num 9) (num 10) (num 11)] ^
                 [(num 12) (num 13) a])
   { nums &stop=14 | partition 3 &pad=[a] | blast }

   "The size of the pad may exceed what is used."
   (test:assert-each [(num 0) (num 1) (num 2)] ^
                 [(num 3) (num 4) (num 5)] ^
                 [(num 6) (num 7) (num 8)] ^
                 [(num 9) (num 10) (num 11)] ^
                 [(num 12) a b])
   { nums &stop=13 | partition 3 &pad=[a b] | blast }

   "...or not."
   (test:assert-each [(num 0) (num 1) (num 2)] ^
                 [(num 3) (num 4) (num 5)] ^
                 [(num 6) (num 7) (num 8)] ^
                 [(num 9) (num 10) (num 11)] ^
                 [(num 12)])
   { nums &stop=13 | partition 3 &pad=[] | blast }]

  [partition-all
   'Convenience function for `partition` which supplies `&pad=[]`.'
   "Use when you don't want everything in the resultset."
   (test:assert-each [(num 0) (num 1) (num 2)] ^
                 [(num 3) (num 4) (num 5)] ^
                 [(num 6) (num 7) (num 8)] ^
                 [(num 9) (num 10) (num 11)] ^
                 [(num 12)])
   { nums &stop=13 | partition-all 3 | blast }]

  [take-nth
   'Returns the nth element from the given iterator.'
   (test:assert-each (range 50 | fun:take-nth 5))
   { nums &stop=50 | take-nth 5 | blast }]

  [drop-last
   'Drops the last `n` elements from an iterator.'
   (test:assert-each (range 5))
   { nums &stop=10 | drop-last 5 | blast }]

  [butlast
   'Drops the last element from an iterator'
   (test:assert-each (range 4))
   { nums &stop=5 | butlast | blast }]

  [keep-indexed
   'Returns all non-empty & non-nil results of `(f index item)`.'
   (test:assert-each b d f)
   { to-iter a b c d e f g | keep-indexed {|i x| if (base:is-odd $i) { put $x } else { put $nil }} | blast }

   'And supply your own predicate.'
   (test:assert-each [(num 1) b] [(num 3) d] [(num 5) f])
   { to-iter a b c d e f g | keep-indexed {|i x| put [$i $x]} &pred=(fun:comp $base:first~ $base:is-odd~) | blast }]

  '# consumers'
  [blast
   'Simplest consumer.  "Blasts" the iterator output to the terminal.'
   (test:assert-each (range 10))
   { range 10 | to-iter | blast }]

  [first
   'Returns the first element from an iterator.'
   (test:assert-one (num 0))
   { nums | first }]

  [second
   'Returns the second element from an iterator.'
   (test:assert-one (num 1))
   { nums | second }]

  [nth
   'Returns the nth element from an iterator'
   (test:assert-one (num 24))
   { nums | nth 25 }]

  [some
   'Returns the first truthy value from `(f x)`.'
   (test:assert-one $true)
   { nums &stop=20 | some {|i| < $i 50} }
   (test:assert-one $false)
   { nums &stop=20 | some {|i| > $i 50} }
   (test:assert-one (num 0))
   { nums &stop=20 | some {|i| if (< $i 50) { put $i } } }
   'Might return nothing, if nothing fits.'
   (test:assert-nothing)
   { nums &stop=20 | some {|i| if (> $i 50) { put $i } } }]

  [first-pred
   'Like filter but returns the first value.'
   (test:assert-one (num 0))
   { nums &stop=20 | first-pred {|i| < $i 50} }
   (test:assert-one (num 51))
   { nums | first-pred {|i| > $i 50} }
   (test:assert-nothing)
   { nums &stop=20 | first-pred {|i| > $i 50} }]

  [every
   'Returns `$true` if every element satisfies the predicate.  `$false` otherwise.'
   (test:assert-one $true)
   { nums &stop=20 | every {|i| < $i 50} }
   (test:assert-one $false)
   { nums | every {|i| < $i 50} }]]