Initial commit
This commit is contained in:
commit
8c5aa8092d
7 changed files with 1462 additions and 0 deletions
23
.github/workflows/test.yml
vendored
Normal file
23
.github/workflows/test.yml
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
name: test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: erlef/setup-beam@v1
|
||||
with:
|
||||
otp-version: "27.1.2"
|
||||
gleam-version: "1.11.1"
|
||||
rebar3-version: "3"
|
||||
# elixir-version: "1"
|
||||
- run: gleam deps download
|
||||
- run: gleam test
|
||||
- run: gleam format --check src test
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.beam
|
||||
*.ez
|
||||
/build
|
||||
erl_crash.dump
|
26
README.md
Normal file
26
README.md
Normal file
|
@ -0,0 +1,26 @@
|
|||
# infiniyield
|
||||
|
||||
[](https://hex.pm/packages/infiniyield)
|
||||
[](https://hexdocs.pm/infiniyield/)
|
||||
|
||||
```sh
|
||||
gleam add infiniyield@1
|
||||
```
|
||||
```gleam
|
||||
import infiniyield
|
||||
|
||||
pub fn main() {
|
||||
infiniyield.unfold(2, fn(acc) { yielder.Next(acc, acc * 2) })
|
||||
|> infiniyield.take(5)
|
||||
// -> [2, 4, 8, 16, 32]
|
||||
}
|
||||
```
|
||||
|
||||
Further documentation can be found at <https://hexdocs.pm/infiniyield>.
|
||||
|
||||
## Development
|
||||
|
||||
```sh
|
||||
gleam run # Run the project
|
||||
gleam test # Run the tests
|
||||
```
|
11
gleam.toml
Normal file
11
gleam.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
name = "infiniyield"
|
||||
version = "1.0.0"
|
||||
description = "Infinitely unfold values on-demand from a function"
|
||||
licences = ["Apache-2.0"]
|
||||
repository = { type = "forgejo", host = "git.7cs.dev", user = "lily", repo = "infiniyield" }
|
||||
|
||||
[dependencies]
|
||||
gleam_stdlib = ">= 0.44.0 and < 2.0.0"
|
||||
|
||||
[dev-dependencies]
|
||||
gleeunit = ">= 1.0.0 and < 2.0.0"
|
11
manifest.toml
Normal file
11
manifest.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
# This file was generated by Gleam
|
||||
# You typically do not need to edit this file
|
||||
|
||||
packages = [
|
||||
{ name = "gleam_stdlib", version = "0.62.1", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "0080706D3A5A9A36C40C68481D1D231D243AF602E6D2A2BE67BA8F8F4DFF45EC" },
|
||||
{ name = "gleeunit", version = "1.6.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "FDC68A8C492B1E9B429249062CD9BAC9B5538C6FBF584817205D0998C42E1DAC" },
|
||||
]
|
||||
|
||||
[requirements]
|
||||
gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" }
|
||||
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
|
960
src/infiniyield.gleam
Normal file
960
src/infiniyield.gleam
Normal file
|
@ -0,0 +1,960 @@
|
|||
import gleam/list
|
||||
|
||||
// Internal private representation of a Yielder
|
||||
type Action(element) {
|
||||
Continue(element, fn() -> Action(element))
|
||||
}
|
||||
|
||||
/// An yielder is a lazily evaluated infinite sequence of elements.
|
||||
///
|
||||
/// As a lazy data structure no work is done when an yielder is filtered,
|
||||
/// mapped, etc, instead a new yielder is returned with these transformations
|
||||
/// applied to the stream. Once the stream has all the required transformations
|
||||
/// applied it can be evaluated using functions such as `take` and `step`.
|
||||
///
|
||||
pub opaque type Yielder(element) {
|
||||
Yielder(continuation: fn() -> Action(element))
|
||||
}
|
||||
|
||||
// Public API for iteration
|
||||
pub type Step(element, accumulator) {
|
||||
Next(element: element, accumulator: accumulator)
|
||||
}
|
||||
|
||||
/// Creates an yielder from a given function and accumulator.
|
||||
///
|
||||
/// The function is called on the accumulator and returns `Next` which contains a
|
||||
/// new element and accumulator. The element is yielded by the yielder and the
|
||||
/// new accumulator is used with the function to compute the next element in
|
||||
/// the sequence.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// unfold(from: 5, with: fn(n) {
|
||||
/// case n {
|
||||
/// n -> Next(element: n, accumulator: n - 1)
|
||||
/// }
|
||||
/// })
|
||||
/// |> take(5)
|
||||
/// // -> [5, 4, 3, 2, 1]
|
||||
/// ```
|
||||
///
|
||||
pub fn unfold(
|
||||
from initial: acc,
|
||||
with f: fn(acc) -> Step(element, acc),
|
||||
) -> Yielder(element) {
|
||||
initial
|
||||
|> unfold_loop(f)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn unfold_loop(
|
||||
initial: acc,
|
||||
f: fn(acc) -> Step(element, acc),
|
||||
) -> fn() -> Action(element) {
|
||||
fn() {
|
||||
let Next(x, acc) = f(initial)
|
||||
Continue(x, unfold_loop(acc, f))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder that yields values created by calling a given function
|
||||
/// repeatedly.
|
||||
///
|
||||
/// ```gleam
|
||||
/// repeatedly(fn() { 7 })
|
||||
/// |> take(3)
|
||||
/// // -> [7, 7, 7]
|
||||
/// ```
|
||||
///
|
||||
pub fn repeatedly(f: fn() -> element) -> Yielder(element) {
|
||||
unfold(Nil, fn(_) { Next(f(), Nil) })
|
||||
}
|
||||
|
||||
/// Creates an yielder that returns the same value infinitely.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// repeat(10)
|
||||
/// |> take(4)
|
||||
/// // -> [10, 10, 10, 10]
|
||||
/// ```
|
||||
///
|
||||
pub fn repeat(x: element) -> Yielder(element) {
|
||||
repeatedly(fn() { x })
|
||||
}
|
||||
|
||||
/// Creates an yielder from an existing yielder
|
||||
/// and a stateful function that may short-circuit.
|
||||
///
|
||||
/// `f` takes arguments `acc` for current state and `el` for current element from underlying yielder,
|
||||
/// and returns `Next` with the yielded element and new state value.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// Approximate implementation of `index` in terms of `transform`:
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle(["a", "b", "c"])
|
||||
/// |> transform(0, fn(i, el) { Next(#(i, el), i + 1) })
|
||||
/// |> take(3)
|
||||
/// // -> [#(0, "a"), #(1, "b"), #(2, "c")]
|
||||
/// ```
|
||||
///
|
||||
pub fn transform(
|
||||
over yielder: Yielder(a),
|
||||
from initial: acc,
|
||||
with f: fn(acc, a) -> Step(b, acc),
|
||||
) -> Yielder(b) {
|
||||
transform_loop(yielder.continuation, initial, f)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn transform_loop(
|
||||
continuation: fn() -> Action(a),
|
||||
state: acc,
|
||||
f: fn(acc, a) -> Step(b, acc),
|
||||
) -> fn() -> Action(b) {
|
||||
fn() {
|
||||
let Continue(el, next) = continuation()
|
||||
let Next(yield, next_state) = f(state, el)
|
||||
Continue(yield, transform_loop(next, next_state, f))
|
||||
}
|
||||
}
|
||||
|
||||
/// Eagerly accesses the first value of an yielder, returning a `Next`
|
||||
/// that contains the first value and the rest of the yielder.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// let Next(first, rest) = cycle([1, 2, 3, 4]) |> step
|
||||
///
|
||||
/// first
|
||||
/// // -> 1
|
||||
///
|
||||
/// rest |> take(3)
|
||||
/// // -> [2, 3, 4]
|
||||
/// ```
|
||||
///
|
||||
pub fn step(yielder: Yielder(e)) -> Step(e, Yielder(e)) {
|
||||
let Continue(e, a) = yielder.continuation()
|
||||
Next(e, Yielder(a))
|
||||
}
|
||||
|
||||
/// Creates an yielder that only yields the first `desired` elements.
|
||||
///
|
||||
/// If the yielder does not have enough elements all of them are yielded.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4, 5])
|
||||
/// |> take(3)
|
||||
/// // -> [1, 2, 3]
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2])
|
||||
/// |> take(3)
|
||||
/// // -> [1, 2, 1]
|
||||
/// ```
|
||||
///
|
||||
pub fn take(yielder: Yielder(e), desired: Int) -> List(e) {
|
||||
yielder.continuation
|
||||
|> take_loop(desired, [])
|
||||
}
|
||||
|
||||
fn take_loop(
|
||||
continuation: fn() -> Action(e),
|
||||
desired: Int,
|
||||
result: List(e),
|
||||
) -> List(e) {
|
||||
case desired > 0 {
|
||||
False -> list.reverse(result)
|
||||
True -> {
|
||||
let Continue(e, next) = continuation()
|
||||
take_loop(next, desired - 1, [e, ..result])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates and discards the first N elements in an yielder, returning a new
|
||||
/// yielder.
|
||||
///
|
||||
/// This function does not evaluate the elements of the yielder, the
|
||||
/// computation is performed when the yielder is later run.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4, 5])
|
||||
/// |> drop(3)
|
||||
/// |> take(3)
|
||||
/// // -> [4, 5]
|
||||
/// ```
|
||||
///
|
||||
pub fn drop(yielder: Yielder(e), desired: Int) -> Yielder(e) {
|
||||
fn() { drop_loop(yielder.continuation, desired) }
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn drop_loop(continuation: fn() -> Action(e), desired: Int) -> Action(e) {
|
||||
let Continue(e, next) = continuation()
|
||||
case desired > 0 {
|
||||
True -> drop_loop(next, desired - 1)
|
||||
False -> Continue(e, next)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder from an existing yielder and a transformation function.
|
||||
///
|
||||
/// Each element in the new yielder will be the result of calling the given
|
||||
/// function on the elements in the given yielder.
|
||||
///
|
||||
/// This function does not evaluate the elements of the yielder, the
|
||||
/// computation is performed when the yielder is later run.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3])
|
||||
/// |> map(fn(x) { x * 2 })
|
||||
/// |> take(3)
|
||||
/// // -> [2, 4, 6]
|
||||
/// ```
|
||||
///
|
||||
pub fn map(over yielder: Yielder(a), with f: fn(a) -> b) -> Yielder(b) {
|
||||
yielder.continuation
|
||||
|> map_loop(f)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn map_loop(continuation: fn() -> Action(a), f: fn(a) -> b) -> fn() -> Action(b) {
|
||||
fn() {
|
||||
let Continue(e, continuation) = continuation()
|
||||
Continue(f(e), map_loop(continuation, f))
|
||||
}
|
||||
}
|
||||
|
||||
/// Combines two yielders into a single one using the given function.
|
||||
///
|
||||
/// If an yielder is longer than the other the extra elements are dropped.
|
||||
///
|
||||
/// This function does not evaluate the elements of the two yielders, the
|
||||
/// computation is performed when the resulting yielder is later run.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// let first = cycle([1, 2, 3])
|
||||
/// let second = cycle([4, 5, 6])
|
||||
/// map2(first, second, fn(x, y) { x + y }) |> take(3)
|
||||
/// // -> [5, 7, 9]
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// let first = cycle([1, 2])
|
||||
/// let second = cycle(["a", "b", "c"])
|
||||
/// map2(first, second, fn(i, x) { #(i, x) }) |> take(2)
|
||||
/// // -> [#(1, "a"), #(2, "b")]
|
||||
/// ```
|
||||
///
|
||||
pub fn map2(
|
||||
yielder1: Yielder(a),
|
||||
yielder2: Yielder(b),
|
||||
with fun: fn(a, b) -> c,
|
||||
) -> Yielder(c) {
|
||||
map2_loop(yielder1.continuation, yielder2.continuation, fun)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn map2_loop(
|
||||
continuation1: fn() -> Action(a),
|
||||
continuation2: fn() -> Action(b),
|
||||
with fun: fn(a, b) -> c,
|
||||
) -> fn() -> Action(c) {
|
||||
fn() {
|
||||
let Continue(a, next_a) = continuation1()
|
||||
let Continue(b, next_b) = continuation2()
|
||||
Continue(fun(a, b), map2_loop(next_a, next_b, fun))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder from an existing yielder and a predicate function.
|
||||
///
|
||||
/// The new yielder will contain elements from the first yielder for which
|
||||
/// the given function returns `True`.
|
||||
///
|
||||
/// This function does not evaluate the elements of the yielder, the
|
||||
/// computation is performed when the yielder is later run.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// import gleam/int
|
||||
///
|
||||
/// cycle([1, 2, 3, 4])
|
||||
/// |> filter(int.is_even)
|
||||
/// |> take(2)
|
||||
/// // -> [2, 4]
|
||||
/// ```
|
||||
///
|
||||
pub fn filter(
|
||||
yielder: Yielder(a),
|
||||
keeping predicate: fn(a) -> Bool,
|
||||
) -> Yielder(a) {
|
||||
fn() { filter_loop(yielder.continuation, predicate) }
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn filter_loop(
|
||||
continuation: fn() -> Action(e),
|
||||
predicate: fn(e) -> Bool,
|
||||
) -> Action(e) {
|
||||
let Continue(e, yielder) = continuation()
|
||||
case predicate(e) {
|
||||
True -> Continue(e, fn() { filter_loop(yielder, predicate) })
|
||||
False -> filter_loop(yielder, predicate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder from an existing yielder and a transforming predicate function.
|
||||
///
|
||||
/// The new yielder will contain elements from the first yielder for which
|
||||
/// the given function returns `Ok`, transformed to the value inside the `Ok`.
|
||||
///
|
||||
/// This function does not evaluate the elements of the yielder, the
|
||||
/// computation is performed when the yielder is later run.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// import gleam/string
|
||||
/// import gleam/int
|
||||
///
|
||||
/// "a1b2c3d4e5f"
|
||||
/// |> string.to_graphemes
|
||||
/// |> cycle
|
||||
/// |> filter_map(int.parse)
|
||||
/// |> take(5)
|
||||
/// // -> [1, 2, 3, 4, 5]
|
||||
/// ```
|
||||
///
|
||||
pub fn filter_map(
|
||||
yielder: Yielder(a),
|
||||
keeping_with f: fn(a) -> Result(b, c),
|
||||
) -> Yielder(b) {
|
||||
fn() { filter_map_loop(yielder.continuation, f) }
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn filter_map_loop(
|
||||
continuation: fn() -> Action(a),
|
||||
f: fn(a) -> Result(b, c),
|
||||
) -> Action(b) {
|
||||
let Continue(e, next) = continuation()
|
||||
case f(e) {
|
||||
Ok(e) -> Continue(e, fn() { filter_map_loop(next, f) })
|
||||
Error(_) -> filter_map_loop(next, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder that repeats a given list infinitely.
|
||||
///
|
||||
/// If an empty list is provided, attempting to yield a value
|
||||
/// from the yielder will result in an infinite loop.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2]) |> take(6)
|
||||
/// // -> [1, 2, 1, 2, 1, 2]
|
||||
/// ```
|
||||
///
|
||||
pub fn cycle(list: List(a)) -> Yielder(a) {
|
||||
fn() { cycle_loop(list, list) } |> Yielder
|
||||
}
|
||||
|
||||
fn cycle_loop(list: List(a), cur: List(a)) -> Action(a) {
|
||||
case cur {
|
||||
[] -> cycle_loop(list, list)
|
||||
[head, ..tail] -> Continue(head, fn() { cycle_loop(list, tail) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder of ints, starting at a given start int
|
||||
/// and incrementing by one each time.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// incrementing(from: 1) |> take(5)
|
||||
/// // -> [1, 2, 3, 4, 5]
|
||||
/// ```
|
||||
///
|
||||
pub fn incrementing(from start: Int) -> Yielder(Int) {
|
||||
unfold(from: start, with: fn(current) { Next(current, current + 1) })
|
||||
}
|
||||
|
||||
/// Creates an yielder of ints, starting at a given start int
|
||||
/// and decrementing by one each time.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// incrementing(from: 5) |> take(5)
|
||||
/// // -> [5, 4, 3, 2, 1]
|
||||
/// ```
|
||||
///
|
||||
pub fn decrementing(from start: Int) -> Yielder(Int) {
|
||||
unfold(from: start, with: fn(current) { Next(current, current - 1) })
|
||||
}
|
||||
|
||||
/// Finds the first element in a given yielder for which the given function returns
|
||||
/// `True`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// find(cycle([1, 2, 3]), fn(x) { x > 2 })
|
||||
/// // -> Ok(3)
|
||||
/// ```
|
||||
///
|
||||
pub fn find(in haystack: Yielder(a), one_that is_desired: fn(a) -> Bool) -> a {
|
||||
haystack.continuation
|
||||
|> find_loop(is_desired)
|
||||
}
|
||||
|
||||
fn find_loop(continuation: fn() -> Action(a), f: fn(a) -> Bool) -> a {
|
||||
let Continue(e, next) = continuation()
|
||||
case f(e) {
|
||||
True -> e
|
||||
False -> find_loop(next, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Finds the first element in a given yielder
|
||||
/// for which the given function returns `Ok(new_value)`,
|
||||
/// then returns the `new_value`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// find_map(cycle(["a", "1", "2"]), int.parse)
|
||||
/// // -> Ok(1)
|
||||
/// ```
|
||||
///
|
||||
pub fn find_map(
|
||||
in haystack: Yielder(a),
|
||||
one_that is_desired: fn(a) -> Result(b, c),
|
||||
) -> b {
|
||||
haystack.continuation
|
||||
|> find_map_loop(is_desired)
|
||||
}
|
||||
|
||||
fn find_map_loop(continuation: fn() -> Action(a), f: fn(a) -> Result(b, c)) -> b {
|
||||
let Continue(e, next) = continuation()
|
||||
case f(e) {
|
||||
Ok(e) -> e
|
||||
Error(_) -> find_map_loop(next, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps values yielded from an yielder with indices, starting from 0.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle(["a", "b", "c"]) |> index |> take(3)
|
||||
/// // -> [#("a", 0), #("b", 1), #("c", 2)]
|
||||
/// ```
|
||||
///
|
||||
pub fn index(over yielder: Yielder(element)) -> Yielder(#(element, Int)) {
|
||||
yielder.continuation
|
||||
|> index_loop(0)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn index_loop(
|
||||
continuation: fn() -> Action(element),
|
||||
next: Int,
|
||||
) -> fn() -> Action(#(element, Int)) {
|
||||
fn() {
|
||||
let Continue(e, continuation) = continuation()
|
||||
Continue(#(e, next), index_loop(continuation, next + 1))
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder that infinitely applies a function to a value.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// iterate(1, fn(n) { n * 3 }) |> take(5)
|
||||
/// // -> [1, 3, 9, 27, 81]
|
||||
/// ```
|
||||
///
|
||||
pub fn iterate(
|
||||
from initial: element,
|
||||
with f: fn(element) -> element,
|
||||
) -> Yielder(element) {
|
||||
unfold(initial, fn(element) { Next(element, f(element)) })
|
||||
}
|
||||
|
||||
/// Creates an yielder that yields elements while the predicate returns `True`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 2, 4])
|
||||
/// |> take_while(satisfying: fn(x) { x < 3 })
|
||||
/// |> take(2)
|
||||
/// // -> [1, 2]
|
||||
/// ```
|
||||
///
|
||||
pub fn take_while(
|
||||
in yielder: Yielder(e),
|
||||
satisfying predicate: fn(e) -> Bool,
|
||||
) -> List(e) {
|
||||
yielder.continuation
|
||||
|> take_while_loop(predicate, [])
|
||||
}
|
||||
|
||||
fn take_while_loop(
|
||||
continuation: fn() -> Action(e),
|
||||
predicate: fn(e) -> Bool,
|
||||
result: List(e),
|
||||
) -> List(e) {
|
||||
let Continue(e, next) = continuation()
|
||||
case predicate(e) {
|
||||
False -> list.reverse(result)
|
||||
True -> take_while_loop(next, predicate, [e, ..result])
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder that drops elements while the predicate returns `True`,
|
||||
/// and then yields the remaining elements.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4, 2, 5])
|
||||
/// |> drop_while(satisfying: fn(x) { x < 4 })
|
||||
/// |> take(3)
|
||||
/// // -> [4, 2, 5]
|
||||
/// ```
|
||||
///
|
||||
pub fn drop_while(
|
||||
in yielder: Yielder(element),
|
||||
satisfying predicate: fn(element) -> Bool,
|
||||
) -> Yielder(element) {
|
||||
fn() { drop_while_loop(yielder.continuation, predicate) }
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn drop_while_loop(
|
||||
continuation: fn() -> Action(element),
|
||||
predicate: fn(element) -> Bool,
|
||||
) -> Action(element) {
|
||||
let Continue(e, next) = continuation()
|
||||
case predicate(e) {
|
||||
False -> Continue(e, next)
|
||||
True -> drop_while_loop(next, predicate)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder from an existing yielder and a stateful function.
|
||||
///
|
||||
/// Specifically, this behaves like `fold`, but yields intermediate results.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// // Generate a sequence of partial sums
|
||||
/// cycle([1, 2, 3, 4, 5])
|
||||
/// |> scan(from: 0, with: fn(acc, el) { acc + el })
|
||||
/// |> take(5)
|
||||
/// // -> [1, 3, 6, 10, 15]
|
||||
/// ```
|
||||
///
|
||||
pub fn scan(
|
||||
over yielder: Yielder(element),
|
||||
from initial: acc,
|
||||
with f: fn(acc, element) -> acc,
|
||||
) -> Yielder(acc) {
|
||||
yielder.continuation
|
||||
|> scan_loop(f, initial)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn scan_loop(
|
||||
continuation: fn() -> Action(element),
|
||||
f: fn(acc, element) -> acc,
|
||||
accumulator: acc,
|
||||
) -> fn() -> Action(acc) {
|
||||
fn() {
|
||||
let Continue(el, next) = continuation()
|
||||
let accumulated = f(accumulator, el)
|
||||
Continue(accumulated, scan_loop(next, f, accumulated))
|
||||
}
|
||||
}
|
||||
|
||||
/// Zips two yielders together, emitting values from both
|
||||
/// until the shorter one runs out.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle(["a", "b", "c"])
|
||||
/// |> zip(incrementing(20))
|
||||
/// |> take(3)
|
||||
/// // -> [#("a", 20), #("b", 21), #("c", 22)]
|
||||
/// ```
|
||||
///
|
||||
pub fn zip(left: Yielder(a), right: Yielder(b)) -> Yielder(#(a, b)) {
|
||||
zip_loop(left.continuation, right.continuation)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn zip_loop(
|
||||
left: fn() -> Action(a),
|
||||
right: fn() -> Action(b),
|
||||
) -> fn() -> Action(#(a, b)) {
|
||||
fn() {
|
||||
let Continue(el_left, next_left) = left()
|
||||
let Continue(el_right, next_right) = right()
|
||||
Continue(#(el_left, el_right), zip_loop(next_left, next_right))
|
||||
}
|
||||
}
|
||||
|
||||
// Result of collecting a single chunk by key
|
||||
type Chunk(element, key) {
|
||||
AnotherBy(List(element), key, element, fn() -> Action(element))
|
||||
}
|
||||
|
||||
/// Creates an yielder that emits chunks of elements
|
||||
/// for which `f` returns the same value.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 2, 3, 4, 4, 6, 7, 7])
|
||||
/// |> chunk(by: fn(n) { n % 2 })
|
||||
/// |> take(5)
|
||||
/// // -> [[1], [2, 2], [3], [4, 4, 6], [7, 7, 1]]
|
||||
/// ```
|
||||
///
|
||||
pub fn chunk(
|
||||
over yielder: Yielder(element),
|
||||
by f: fn(element) -> key,
|
||||
) -> Yielder(List(element)) {
|
||||
fn() {
|
||||
let Continue(e, next) = yielder.continuation()
|
||||
chunk_loop(next, f, f(e), e)
|
||||
}
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn chunk_loop(
|
||||
continuation: fn() -> Action(element),
|
||||
f: fn(element) -> key,
|
||||
previous_key: key,
|
||||
previous_element: element,
|
||||
) -> Action(List(element)) {
|
||||
let AnotherBy(chunk, key, el, next) =
|
||||
next_chunk(continuation, f, previous_key, [previous_element])
|
||||
Continue(chunk, fn() { chunk_loop(next, f, key, el) })
|
||||
}
|
||||
|
||||
fn next_chunk(
|
||||
continuation: fn() -> Action(element),
|
||||
f: fn(element) -> key,
|
||||
previous_key: key,
|
||||
current_chunk: List(element),
|
||||
) -> Chunk(element, key) {
|
||||
let Continue(e, next) = continuation()
|
||||
let key = f(e)
|
||||
case key == previous_key {
|
||||
True -> next_chunk(next, f, key, [e, ..current_chunk])
|
||||
False -> AnotherBy(list.reverse(current_chunk), key, e, next)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder that emits chunks of given size.
|
||||
///
|
||||
/// If the last chunk does not have `count` elements, it is yielded
|
||||
/// as a partial chunk, with less than `count` elements.
|
||||
///
|
||||
/// For any `count` less than 1 this function behaves as if it was set to 1.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4, 5, 6])
|
||||
/// |> sized_chunk(into: 2)
|
||||
/// |> take(3)
|
||||
/// // -> [[1, 2], [3, 4], [5, 6]]
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
/// |> sized_chunk(into: 3)
|
||||
/// |> take(3)
|
||||
/// // -> [[1, 2, 3], [4, 5, 6], [7, 8, 1]]
|
||||
/// ```
|
||||
///
|
||||
pub fn sized_chunk(
|
||||
over yielder: Yielder(element),
|
||||
into count: Int,
|
||||
) -> Yielder(List(element)) {
|
||||
yielder.continuation
|
||||
|> sized_chunk_loop(count)
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn sized_chunk_loop(
|
||||
continuation: fn() -> Action(element),
|
||||
count: Int,
|
||||
) -> fn() -> Action(List(element)) {
|
||||
fn() {
|
||||
let Another(chunk, next_element) = next_sized_chunk(continuation, count, [])
|
||||
Continue(chunk, sized_chunk_loop(next_element, count))
|
||||
}
|
||||
}
|
||||
|
||||
// Result of collecting a single sized chunk
|
||||
type SizedChunk(element) {
|
||||
Another(List(element), fn() -> Action(element))
|
||||
}
|
||||
|
||||
fn next_sized_chunk(
|
||||
continuation: fn() -> Action(element),
|
||||
left: Int,
|
||||
current_chunk: List(element),
|
||||
) -> SizedChunk(element) {
|
||||
let Continue(e, next) = continuation()
|
||||
let chunk = [e, ..current_chunk]
|
||||
case left > 1 {
|
||||
False -> Another(list.reverse(chunk), next)
|
||||
True -> next_sized_chunk(next, left - 1, chunk)
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an yielder that yields the given `elem` element
|
||||
/// between elements emitted by the underlying yielder.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1])
|
||||
/// |> intersperse(with: 0)
|
||||
/// |> take(1)
|
||||
/// // -> [1]
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4, 5])
|
||||
/// |> intersperse(with: 0)
|
||||
/// |> take(9)
|
||||
/// // -> [1, 0, 2, 0, 3, 0, 4, 0, 5]
|
||||
/// ```
|
||||
///
|
||||
pub fn intersperse(
|
||||
over yielder: Yielder(element),
|
||||
with elem: element,
|
||||
) -> Yielder(element) {
|
||||
fn() {
|
||||
let Continue(e, next) = yielder.continuation()
|
||||
Continue(e, fn() { intersperse_loop(next, elem) })
|
||||
}
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn intersperse_loop(
|
||||
continuation: fn() -> Action(element),
|
||||
separator: element,
|
||||
) -> Action(element) {
|
||||
let Continue(e, next) = continuation()
|
||||
let next_interspersed = fn() { intersperse_loop(next, separator) }
|
||||
Continue(separator, fn() { Continue(e, next_interspersed) })
|
||||
}
|
||||
|
||||
/// Creates an yielder that alternates between the two given yielders.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4])
|
||||
/// |> interleave(cycle([11, 12, 13, 14]))
|
||||
/// |> take(8)
|
||||
/// // -> [1, 11, 2, 12, 3, 13, 4, 14]
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4])
|
||||
/// |> interleave(cycle([100]))
|
||||
/// |> take(8)
|
||||
/// // -> [1, 100, 2, 100, 3, 100, 4, 100]
|
||||
/// ```
|
||||
///
|
||||
pub fn interleave(
|
||||
left: Yielder(element),
|
||||
with right: Yielder(element),
|
||||
) -> Yielder(element) {
|
||||
fn() { interleave_loop(left.continuation, right.continuation) }
|
||||
|> Yielder
|
||||
}
|
||||
|
||||
fn interleave_loop(
|
||||
current: fn() -> Action(element),
|
||||
next: fn() -> Action(element),
|
||||
) -> Action(element) {
|
||||
let Continue(e, next_other) = current()
|
||||
Continue(e, fn() { interleave_loop(next, next_other) })
|
||||
}
|
||||
|
||||
/// Reduces a yielder of elements into a single value by calling a given
|
||||
/// function on each element in turn, using `list.ContinueOrStop` to determine
|
||||
/// whether or not to keep iterating.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// import gleam/list
|
||||
///
|
||||
/// let f = fn(acc, e) {
|
||||
/// case e {
|
||||
/// _ if e < 4 -> list.Continue(e + acc)
|
||||
/// _ -> list.Stop(acc)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// cycle([1, 2, 3, 4])
|
||||
/// |> fold_until(from: 0, with: f)
|
||||
/// // -> 6
|
||||
/// ```
|
||||
///
|
||||
pub fn fold_until(
|
||||
over yielder: Yielder(e),
|
||||
from initial: acc,
|
||||
with f: fn(acc, e) -> list.ContinueOrStop(acc),
|
||||
) -> acc {
|
||||
yielder.continuation
|
||||
|> fold_until_loop(f, initial)
|
||||
}
|
||||
|
||||
fn fold_until_loop(
|
||||
continuation: fn() -> Action(e),
|
||||
f: fn(acc, e) -> list.ContinueOrStop(acc),
|
||||
accumulator: acc,
|
||||
) -> acc {
|
||||
let Continue(elem, next) = continuation()
|
||||
case f(accumulator, elem) {
|
||||
list.Continue(accumulator) -> fold_until_loop(next, f, accumulator)
|
||||
list.Stop(accumulator) -> accumulator
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the first element yielded by the given yielder.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3]) |> first
|
||||
/// // -> 1
|
||||
/// ```
|
||||
///
|
||||
pub fn first(from yielder: Yielder(e)) -> e {
|
||||
let Continue(e, _) = yielder.continuation()
|
||||
e
|
||||
}
|
||||
|
||||
/// Returns nth element yielded by the given yielder, where `0` means the first element.
|
||||
///
|
||||
/// For any `index` less than `0` this function behaves as if it was set to `0`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4]) |> at(2)
|
||||
/// // -> 3
|
||||
/// ```
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle([1, 2, 3, 4]) |> at(4)
|
||||
/// // -> 1
|
||||
/// ```
|
||||
///
|
||||
pub fn at(in yielder: Yielder(e), get index: Int) -> e {
|
||||
yielder
|
||||
|> drop(index)
|
||||
|> first
|
||||
}
|
||||
|
||||
/// Transform a yielder into one that calls the given function on each element.
|
||||
///
|
||||
/// Like `map`, but always returns `Nil` for each element after calling the given
|
||||
/// transformation function.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// cycle(["Tom", "Malory", "Louis"]) |> each(io.println) |> take(3)
|
||||
/// // -> Nil
|
||||
/// // Tom
|
||||
/// // Malory
|
||||
/// // Louis
|
||||
/// ```
|
||||
///
|
||||
pub fn each(over yielder: Yielder(a), with f: fn(a) -> b) -> Yielder(Nil) {
|
||||
yielder
|
||||
|> map(fn(a) {
|
||||
f(a)
|
||||
Nil
|
||||
})
|
||||
}
|
||||
|
||||
/// Add a new element to the start of an yielder.
|
||||
///
|
||||
/// This function is for use with `use` expressions, to replicate the behaviour
|
||||
/// of the `yield` keyword found in other languages.
|
||||
///
|
||||
/// If you only need to prepend an element and don't require the `use` syntax,
|
||||
/// use `prepend`.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// let yielder = {
|
||||
/// use <- yield(1)
|
||||
/// use <- yield(2)
|
||||
/// use <- yield(3)
|
||||
/// cycle([0])
|
||||
/// }
|
||||
///
|
||||
/// yielder |> take(6)
|
||||
/// // -> [1, 2, 3, 0, 0, 0]
|
||||
/// ```
|
||||
///
|
||||
pub fn yield(element: a, next: fn() -> Yielder(a)) -> Yielder(a) {
|
||||
Yielder(fn() { Continue(element, fn() { next().continuation() }) })
|
||||
}
|
||||
|
||||
/// Add a new element to the start of an yielder.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```gleam
|
||||
/// let yielder = cycle([1, 2, 3]) |> prepend(0)
|
||||
///
|
||||
/// yielder.take(4)
|
||||
/// // -> [0, 1, 2, 3]
|
||||
/// ```
|
||||
///
|
||||
pub fn prepend(yielder: Yielder(a), element: a) -> Yielder(a) {
|
||||
use <- yield(element)
|
||||
yielder
|
||||
}
|
427
test/infiniyield_test.gleam
Normal file
427
test/infiniyield_test.gleam
Normal file
|
@ -0,0 +1,427 @@
|
|||
import gleam/int
|
||||
import gleam/list
|
||||
import gleeunit
|
||||
import gleeunit/should
|
||||
import infiniyield.{Next}
|
||||
|
||||
pub fn main() {
|
||||
gleeunit.main()
|
||||
}
|
||||
|
||||
pub fn step_test() {
|
||||
let testcase = fn(subject) {
|
||||
let step =
|
||||
subject
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.step
|
||||
|
||||
let assert [h, ..t] = subject
|
||||
let Next(h2, t2) = step
|
||||
h
|
||||
|> should.equal(h2)
|
||||
t2
|
||||
|> infiniyield.take(list.length(t))
|
||||
|> should.equal(t)
|
||||
}
|
||||
|
||||
testcase([1])
|
||||
testcase([1, 2])
|
||||
testcase([1, 2, 3])
|
||||
}
|
||||
|
||||
// a |> cycle |> take(n) == a |> list.take(_, n)
|
||||
pub fn take_test() {
|
||||
let testcase = fn(n, subject) {
|
||||
subject
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.take(n)
|
||||
|> should.equal(list.take(subject, n))
|
||||
}
|
||||
|
||||
testcase(0, [])
|
||||
testcase(-1, [])
|
||||
testcase(0, [0])
|
||||
testcase(1, [0])
|
||||
testcase(-1, [0])
|
||||
testcase(0, [0, 1, 2, 3, 4])
|
||||
testcase(1, [0, 1, 2, 3, 4])
|
||||
testcase(2, [0, 1, 2, 3, 4])
|
||||
}
|
||||
|
||||
pub fn transform_index_test() {
|
||||
let f = fn(i, el) { Next(#(i, el), i + 1) }
|
||||
|
||||
["a", "b", "c", "d"]
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.transform(0, f)
|
||||
|> infiniyield.take(4)
|
||||
|> should.equal([#(0, "a"), #(1, "b"), #(2, "c"), #(3, "d")])
|
||||
}
|
||||
|
||||
pub fn transform_scan_test() {
|
||||
let f = fn(acc, el) {
|
||||
let result = acc + el
|
||||
Next(result, result)
|
||||
}
|
||||
|
||||
[1, 2, 3, 4, 5]
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.transform(0, f)
|
||||
|> infiniyield.take(5)
|
||||
|> should.equal([1, 3, 6, 10, 15])
|
||||
}
|
||||
|
||||
// a |> cycle |> map(f) == a |> list.map(_, f)
|
||||
pub fn map_test() {
|
||||
let testcase = fn(subject, f) {
|
||||
subject
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.map(f)
|
||||
|> infiniyield.take(list.length(subject))
|
||||
|> should.equal(list.map(subject, f))
|
||||
}
|
||||
|
||||
let f = fn(e) { e * 2 }
|
||||
testcase([], f)
|
||||
testcase([1], f)
|
||||
testcase([1, 2, 3], f)
|
||||
testcase([1, 2, 3, 4, 5, 6, 7, 8], f)
|
||||
}
|
||||
|
||||
// map2(cycle(a), cycle(b), f) == list.map2(a, b, f)
|
||||
pub fn map2_test() {
|
||||
let testcase = fn(one, other, f) {
|
||||
infiniyield.map2(infiniyield.cycle(one), infiniyield.cycle(other), f)
|
||||
|> infiniyield.take(int.min(list.length(one), list.length(other)))
|
||||
|> should.equal(list.map2(one, other, f))
|
||||
}
|
||||
|
||||
let f = fn(a, b) { a / b }
|
||||
testcase([], [], f)
|
||||
testcase([], [2, 10, 3], f)
|
||||
testcase([10], [2, 10, 3], f)
|
||||
testcase([10, 20], [2, 10, 3], f)
|
||||
testcase([10, 20, 30], [2, 10, 3], f)
|
||||
testcase([10, 20, 30], [2, 10], f)
|
||||
testcase([10, 20, 30], [2], f)
|
||||
testcase([10, 20, 30], [], f)
|
||||
}
|
||||
|
||||
pub fn map2_is_lazy_test() {
|
||||
let one = infiniyield.cycle([])
|
||||
let other = infiniyield.repeatedly(fn() { panic as "unreachable" })
|
||||
|
||||
infiniyield.map2(one, other, fn(x, y) { x + y })
|
||||
|> infiniyield.take(0)
|
||||
|> should.equal([])
|
||||
}
|
||||
|
||||
// a |> cycle |> filter(f) == a |> list.filter(_, f)
|
||||
pub fn filter_test() {
|
||||
let testcase = fn(subject, f) {
|
||||
let lst = list.filter(subject, f)
|
||||
subject
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.filter(f)
|
||||
|> infiniyield.take(list.length(lst))
|
||||
|> should.equal(lst)
|
||||
}
|
||||
|
||||
let even = fn(x) { x % 2 == 0 }
|
||||
testcase([], even)
|
||||
testcase([1], even)
|
||||
testcase([1, 2], even)
|
||||
testcase([1, 2, 3], even)
|
||||
testcase([1, 2, 3, 4], even)
|
||||
testcase([1, 2, 3, 4, 5], even)
|
||||
testcase([1, 2, 3, 4, 5, 6], even)
|
||||
}
|
||||
|
||||
pub fn filter_map_test() {
|
||||
let testcase = fn(subject, f) {
|
||||
let lst = list.filter_map(subject, f)
|
||||
subject
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.filter_map(f)
|
||||
|> infiniyield.take(list.length(lst))
|
||||
|> should.equal(lst)
|
||||
}
|
||||
|
||||
testcase([], int.parse)
|
||||
testcase(["1"], int.parse)
|
||||
testcase(["1", "2", "3"], int.parse)
|
||||
testcase(["1", "a", "b"], int.parse)
|
||||
testcase(["l", "2", "3", "a"], int.parse)
|
||||
testcase(["1", "c", "3", "a", "b"], int.parse)
|
||||
testcase(["1", "20", "ten", "4", "5", "69"], int.parse)
|
||||
}
|
||||
|
||||
pub fn repeat_test() {
|
||||
1
|
||||
|> infiniyield.repeat
|
||||
|> infiniyield.take(5)
|
||||
|> should.equal([1, 1, 1, 1, 1])
|
||||
}
|
||||
|
||||
pub fn cycle_test() {
|
||||
[1, 2, 3]
|
||||
|> infiniyield.cycle
|
||||
|> infiniyield.take(9)
|
||||
|> should.equal([1, 2, 3, 1, 2, 3, 1, 2, 3])
|
||||
}
|
||||
|
||||
pub fn unfold_test() {
|
||||
infiniyield.unfold(2, fn(acc) { infiniyield.Next(acc, acc * 2) })
|
||||
|> infiniyield.take(5)
|
||||
|> should.equal([2, 4, 8, 16, 32])
|
||||
}
|
||||
|
||||
pub fn incrementing_test() {
|
||||
let testcase = fn(a, expected) {
|
||||
infiniyield.incrementing(from: a)
|
||||
|> infiniyield.take(list.length(expected))
|
||||
|> should.equal(expected)
|
||||
}
|
||||
|
||||
testcase(0, [0])
|
||||
testcase(1, [1])
|
||||
testcase(-1, [-1])
|
||||
testcase(0, [0, 1])
|
||||
testcase(0, [0, 1, 2, 3, 4, 5])
|
||||
testcase(1, [1, 2, 3, 4, 5])
|
||||
}
|
||||
|
||||
pub fn deccrementing_test() {
|
||||
let testcase = fn(a, expected) {
|
||||
infiniyield.decrementing(from: a)
|
||||
|> infiniyield.take(list.length(expected))
|
||||
|> should.equal(expected)
|
||||
}
|
||||
|
||||
testcase(0, [0])
|
||||
testcase(1, [1])
|
||||
testcase(-1, [-1])
|
||||
testcase(0, [0, -1])
|
||||
testcase(0, [0, -1, -2, -3, -4, -5])
|
||||
testcase(1, [1, 0, -1, -2, -3, -4, -5])
|
||||
}
|
||||
|
||||
pub fn drop_test() {
|
||||
infiniyield.incrementing(from: 0)
|
||||
|> infiniyield.drop(5)
|
||||
|> infiniyield.take(6)
|
||||
|> should.equal([5, 6, 7, 8, 9, 10])
|
||||
}
|
||||
|
||||
type Cat {
|
||||
Cat(id: Int)
|
||||
}
|
||||
|
||||
pub fn find_test() {
|
||||
infiniyield.incrementing(from: 0)
|
||||
|> infiniyield.find(fn(e) { e == 5 })
|
||||
|> should.equal(5)
|
||||
|
||||
infiniyield.incrementing(from: 0)
|
||||
|> infiniyield.find(fn(e) { e > 10 })
|
||||
|> should.equal(11)
|
||||
|
||||
infiniyield.unfold(Cat(id: 1), fn(cat: Cat) {
|
||||
infiniyield.Next(cat, Cat(id: cat.id + 1))
|
||||
})
|
||||
|> infiniyield.find(fn(cat: Cat) { cat.id == 10 })
|
||||
|> should.equal(Cat(id: 10))
|
||||
}
|
||||
|
||||
pub fn find_map_test() {
|
||||
infiniyield.incrementing(from: 0)
|
||||
|> infiniyield.find_map(fn(e) {
|
||||
case e == 5 {
|
||||
True -> Ok(e)
|
||||
False -> Error(Nil)
|
||||
}
|
||||
})
|
||||
|> should.equal(5)
|
||||
|
||||
infiniyield.incrementing(from: 0)
|
||||
|> infiniyield.find_map(fn(e) {
|
||||
case e > 10 {
|
||||
True -> Ok(e)
|
||||
False -> Error(Nil)
|
||||
}
|
||||
})
|
||||
|> should.equal(11)
|
||||
|
||||
infiniyield.unfold(Cat(id: 1), fn(cat: Cat) {
|
||||
infiniyield.Next(cat, Cat(id: cat.id + 1))
|
||||
})
|
||||
|> infiniyield.find_map(fn(cat: Cat) {
|
||||
case cat.id == 10 {
|
||||
True -> Ok(cat)
|
||||
False -> Error(Nil)
|
||||
}
|
||||
})
|
||||
|> should.equal(Cat(id: 10))
|
||||
}
|
||||
|
||||
pub fn index_test() {
|
||||
infiniyield.cycle(["a", "b", "c"])
|
||||
|> infiniyield.index
|
||||
|> infiniyield.take(3)
|
||||
|> should.equal([#("a", 0), #("b", 1), #("c", 2)])
|
||||
}
|
||||
|
||||
pub fn iterate_test() {
|
||||
fn(x) { x * 3 }
|
||||
|> infiniyield.iterate(from: 1)
|
||||
|> infiniyield.take(5)
|
||||
|> should.equal([1, 3, 9, 27, 81])
|
||||
}
|
||||
|
||||
pub fn take_while_test() {
|
||||
infiniyield.cycle([1, 2, 3, 2, 4])
|
||||
|> infiniyield.take_while(satisfying: fn(x) { x < 3 })
|
||||
|> should.equal([1, 2])
|
||||
}
|
||||
|
||||
pub fn drop_while_test() {
|
||||
infiniyield.cycle([1, 2, 3, 4, 2, 5])
|
||||
|> infiniyield.drop_while(satisfying: fn(x) { x < 4 })
|
||||
|> infiniyield.take(3)
|
||||
|> should.equal([4, 2, 5])
|
||||
}
|
||||
|
||||
pub fn scan_test() {
|
||||
infiniyield.cycle([1, 2, 3, 4, 5])
|
||||
|> infiniyield.scan(from: 0, with: fn(acc, el) { acc + el })
|
||||
|> infiniyield.take(5)
|
||||
|> should.equal([1, 3, 6, 10, 15])
|
||||
}
|
||||
|
||||
pub fn zip_test() {
|
||||
infiniyield.cycle(["a", "b", "c"])
|
||||
|> infiniyield.zip(infiniyield.incrementing(from: 20))
|
||||
|> infiniyield.take(3)
|
||||
|> should.equal([#("a", 20), #("b", 21), #("c", 22)])
|
||||
}
|
||||
|
||||
pub fn chunk_test() {
|
||||
infiniyield.cycle([1, 2, 2, 3, 4, 4, 6, 7, 7])
|
||||
|> infiniyield.chunk(by: fn(n) { n % 2 })
|
||||
|> infiniyield.take(5)
|
||||
|> should.equal([[1], [2, 2], [3], [4, 4, 6], [7, 7, 1]])
|
||||
}
|
||||
|
||||
pub fn sized_chunk_test() {
|
||||
infiniyield.cycle([1, 2, 3, 4, 5, 6])
|
||||
|> infiniyield.sized_chunk(into: 2)
|
||||
|> infiniyield.take(3)
|
||||
|> should.equal([[1, 2], [3, 4], [5, 6]])
|
||||
|
||||
infiniyield.cycle([1, 2, 3, 4, 5, 6, 7, 8])
|
||||
|> infiniyield.sized_chunk(into: 3)
|
||||
|> infiniyield.take(3)
|
||||
|> should.equal([[1, 2, 3], [4, 5, 6], [7, 8, 1]])
|
||||
}
|
||||
|
||||
pub fn intersperse_test() {
|
||||
infiniyield.cycle([1])
|
||||
|> infiniyield.intersperse(with: 0)
|
||||
|> infiniyield.take(1)
|
||||
|> should.equal([1])
|
||||
|
||||
infiniyield.cycle([1, 2, 3, 4, 5])
|
||||
|> infiniyield.intersperse(with: 0)
|
||||
|> infiniyield.take(9)
|
||||
|> should.equal([1, 0, 2, 0, 3, 0, 4, 0, 5])
|
||||
}
|
||||
|
||||
pub fn interleave_test() {
|
||||
infiniyield.cycle([1, 2, 3, 4])
|
||||
|> infiniyield.interleave(with: infiniyield.cycle([11, 12, 13, 14]))
|
||||
|> infiniyield.take(8)
|
||||
|> should.equal([1, 11, 2, 12, 3, 13, 4, 14])
|
||||
|
||||
infiniyield.cycle([1, 2, 3, 4])
|
||||
|> infiniyield.interleave(with: infiniyield.cycle([100]))
|
||||
|> infiniyield.take(8)
|
||||
|> should.equal([1, 100, 2, 100, 3, 100, 4, 100])
|
||||
}
|
||||
|
||||
// a |> cycle |> fold_until(acc, f) == a |> list.fold_until(acc, f)
|
||||
pub fn fold_until_test() {
|
||||
let testcase = fn(subject, acc, f) {
|
||||
subject
|
||||
|> infiniyield.cycle()
|
||||
|> infiniyield.fold_until(acc, f)
|
||||
|> should.equal(list.fold_until(subject, acc, f))
|
||||
}
|
||||
|
||||
let f = fn(acc, e) {
|
||||
case e {
|
||||
_ if e < 6 -> list.Continue([e, ..acc])
|
||||
_ -> list.Stop(acc)
|
||||
}
|
||||
}
|
||||
testcase([1, 2, 3, 4, 5, 6, 7, 8], [], f)
|
||||
|
||||
[1, 2, 3, 4, 5, 6, 7, 8]
|
||||
|> infiniyield.cycle()
|
||||
|> infiniyield.fold_until([], f)
|
||||
|> should.equal([5, 4, 3, 2, 1])
|
||||
}
|
||||
|
||||
pub fn first_test() {
|
||||
infiniyield.cycle([1, 2, 3])
|
||||
|> infiniyield.first
|
||||
|> should.equal(1)
|
||||
}
|
||||
|
||||
pub fn at_test() {
|
||||
infiniyield.cycle([1, 2, 3, 4])
|
||||
|> infiniyield.at(2)
|
||||
|> should.equal(3)
|
||||
|
||||
infiniyield.cycle([1, 2, 3, 4])
|
||||
|> infiniyield.at(4)
|
||||
|> should.equal(1)
|
||||
}
|
||||
|
||||
pub fn each_test() {
|
||||
use it <- infiniyield.each(infiniyield.cycle([1]))
|
||||
it
|
||||
|> should.equal(1)
|
||||
}
|
||||
|
||||
pub fn yield_test() {
|
||||
let items = {
|
||||
use <- infiniyield.yield(1)
|
||||
use <- infiniyield.yield(2)
|
||||
use <- infiniyield.yield(3)
|
||||
infiniyield.repeat(0)
|
||||
}
|
||||
|
||||
items
|
||||
|> infiniyield.take(6)
|
||||
|> should.equal([1, 2, 3, 0, 0, 0])
|
||||
}
|
||||
|
||||
pub fn yield_computes_only_necessary_values_test() {
|
||||
let items = {
|
||||
use <- infiniyield.yield(1)
|
||||
use <- infiniyield.yield(2)
|
||||
use <- infiniyield.yield(3)
|
||||
panic as "yield computed more values than necessary"
|
||||
}
|
||||
|
||||
items
|
||||
|> infiniyield.take(3)
|
||||
|> should.equal([1, 2, 3])
|
||||
}
|
||||
|
||||
pub fn prepend_test() {
|
||||
infiniyield.cycle([1, 2, 3])
|
||||
|> infiniyield.prepend(0)
|
||||
|> infiniyield.take(4)
|
||||
|> should.equal([0, 1, 2, 3])
|
||||
}
|
Loading…
Reference in a new issue