Skip to content

Commit

Permalink
feature: add "unfold" ctor and make Sequence use it as the implementa…
Browse files Browse the repository at this point in the history
…tion.
  • Loading branch information
Dierk Koenig committed Sep 7, 2023
1 parent f18ec64 commit 905b137
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 21 deletions.
2 changes: 1 addition & 1 deletion docs/src/allTests.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<h1>All Tests Report</h1>
<div id="out"></div>

<p> 1244 test(s) expected.</p>
<p> 1247 test(s) expected.</p>
<p id="grossTotal">Check console for possible errors.</p>

<footer></footer>
Expand Down
1 change: 1 addition & 0 deletions docs/src/kolibri/sequence/constructors/constructorTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ import "./seq/seqTest.js";
import "./stackSequence/stackSequenceTest.js";
import "./tupleSequence/tupleSequenceTest.js";
import "./range/rangeTest.js";
import "./unfold/unfoldTest.js";
1 change: 1 addition & 0 deletions docs/src/kolibri/sequence/constructors/constructors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ export * from "./seq/seq.js";
export * from "./replicate/replicate.js";
export * from "./repeat/repeat.js";
export * from "./range/range.js";
export * from "./unfold/unfold.js";
28 changes: 8 additions & 20 deletions docs/src/kolibri/sequence/constructors/sequence/Sequence.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createMonadicSequence } from "../../util/sequenceUtil/createMonadicSequence.js";
import { unfold } from "../unfold/unfold.js";

export { Sequence }

Expand Down Expand Up @@ -28,24 +28,12 @@ export { Sequence }
* // => Logs '0, 1, 2'
*/

const Sequence = (start, whileFunction, incrementFunction) => {
const Sequence = (start, whileFunction, incrementFunction) =>

const iterator = () => {
let value = start;
/**
* @template _T_
* Returns the next iteration of this iterable object.
* @returns { IteratorResult<_T_, _T_> }
*/
const next = () => {
const current = value;
const done = !whileFunction(current);
if (!done) value = incrementFunction(value);
return { done, value: current };
};
unfold(
start,
current => whileFunction(current)
? { state: incrementFunction(current), value: current }
: undefined
);

return { next };
};

return createMonadicSequence(iterator);
};
53 changes: 53 additions & 0 deletions docs/src/kolibri/sequence/constructors/unfold/unfold.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @module kolibri.sequence.constructors.unfold
* The idea was thankfully provided by Daniel Kröni.
*/
import {createMonadicSequence} from "../../util/sequenceUtil/createMonadicSequence.js";

export { unfold }
/**
* @typedef StateAndValueType
* @template _S_, _T_
* @property { _S_ } state
* @property { _T_ } value
*/

/**
* @typedef FromStateToNextStateAndValue
* @callback
* @pure The whole idea of `unfold` is to allow a pure function at this point. Depends on developer discipline.
* @template _S_, _T_
* @param { _S_ } state
* @return { StateAndValueType<_S_, _T_> | undefined } - `undefined` if the sequence cannot produce any more values
*/

/**
* Creates a {@link SequenceType} from a callback function that generates the next state and value from the current state.
* The sequence is supposed to be exhausted when the callback returns `undefined`.
* `unfold` abstracts over the proper state management
* in the closure scope of an {@link Iterable}'s iterator function.
* This allows the {@link FromStateToNextStateAndValue} callback function to be pure.
* @template _T_, _S_
* @param { FromStateToNextStateAndValue<_S_, _T_> } fromStateToNextStateAndValue - callback function to generate the next state and value
* @param { _S_ } initialState
* @return { SequenceType<_T_> }
*/
const unfold = (initialState, fromStateToNextStateAndValue) => {

const iterator = () => {
let runningState = initialState;

const next = () => {
const result = fromStateToNextStateAndValue(runningState);
if (result === undefined) {
return { done: true, value: undefined };
} else {
runningState = result.state;
return { done: false, value: result.value };
}
};
return { next };
};

return createMonadicSequence(iterator);
};
25 changes: 25 additions & 0 deletions docs/src/kolibri/sequence/constructors/unfold/unfoldTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TestSuite } from "../../../util/test.js";
import { unfold } from "./unfold.js";
import { Seq } from "../seq/seq.js";
import { Range } from "../range/range.js";
import { nil } from "../nil/nil.js";

const testSuite = TestSuite("Sequence: constructor unfold");

testSuite.add("common usage", assert => {

const empty = unfold(undefined, _ => undefined);
assert.iterableEq(empty, nil);

const zeroToFour = unfold(0, n => n < 5 ? {state: n + 1, value: n} : undefined);
assert.iterableEq(zeroToFour, Range(4).take(5));

const infiniteFibs = unfold(
{last: 0, current: 1},
( {last, current}) => ({ state: {last: current, current: last + current}, value: last })
);
assert.iterableEq(infiniteFibs.take(10), Seq(0, 1, 1, 2, 3, 5, 8, 13, 21, 34));

});

testSuite.run();

0 comments on commit 905b137

Please sign in to comment.