Skip to content

Commit

Permalink
feat(seq): add lazyseq() & cons(), tests, update readme
Browse files Browse the repository at this point in the history
  • Loading branch information
postspectacular committed Dec 14, 2019
1 parent b7b4724 commit d25584e
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 2 deletions.
68 changes: 67 additions & 1 deletion packages/seq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ This project is part of the
- [Installation](#installation)
- [Dependencies](#dependencies)
- [API](#api)
- [ISeqable support](#iseqable-support)
- [Lazy sequences](#lazy-sequences)
- [Authors](#authors)
- [License](#license)

Expand Down Expand Up @@ -103,15 +105,27 @@ rseq([1, 2, 3]).next().first()
[...iterator(rseq([0, 1, 2, 3, 4], 3, 1))]
// [3, 2]

// values can be prepended via cons()
[...iterator(cons(42, aseq([1, 2, 3])))]
// [ 42, 1, 2, 3 ]

// create new (or single value) seqs
cons(42).first()
// 42
cons(42).next()
// undefined

// zero-copy concat (supporting nullable parts/sub-sequences)
[...iterator(concat(null, aseq([]), aseq([1, 2]), undefined, aseq([3])))]
[...iterator(concat(null, aseq([]), aseq([1, 2]), undefined, cons(3)))]
// [ 1, 2, 3 ]

// if only arrays are used as sources, can also use concatA()
[...iterator(concatA(null, [], [1, 2], undefined, [3]))]
// [ 1, 2, 3 ]
```

### ISeqable support

Since the entire approach is interface based, sequences can be defined
for any custom datatype (preferably via the
[ISeqable](https://github.com/thi-ng/umbrella/blob/master/packages/api/src/api/seq.ts#L35)
Expand All @@ -126,6 +140,58 @@ import { dcons } from "@thi.ng/dcons";
// [ 3, 2, 1, 4, 5, 6 ]
```

### Lazy sequences

Lazily instantiated (possibly infinite) sequences can be created via
`lazyseq()`. This function returns an `ISeq` which only realizes its
values when they're requested.

```ts
import { defmultiN } from "@thi.ng/defmulti";

// defmultiN only used here to define multiple arities for `fib`
const fib = defmultiN({
0: () => fib(0, 1),
2: (a, b) => lazyseq(() => {
console.log(`realize: ${a}`);
return cons(a, fib(b, a + b));
})
});

fib()
// { first: [Function: first], next: [Function: next] }

fib().first()
// realize: 0
// 0

fib().next().first()
// realize: 0
// realize: 1
// 1

fib().next().next().first()
// realize: 0
// realize: 1
// realize: 1
// 1

fib().next().next().next().first()
// realize: 0
// realize: 1
// realize: 1
// realize: 2
// 2

fib().next().next().next().next().first()
// realize: 0
// realize: 1
// realize: 1
// realize: 2
// realize: 3
// 3
```

## Authors

Karsten Schmidt
Expand Down
66 changes: 65 additions & 1 deletion packages/seq/README.tpl.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,15 +100,27 @@ rseq([1, 2, 3]).next().first()
[...iterator(rseq([0, 1, 2, 3, 4], 3, 1))]
// [3, 2]

// values can be prepended via cons()
[...iterator(cons(42, aseq([1, 2, 3])))]
// [ 42, 1, 2, 3 ]

// create new (or single value) seqs
cons(42).first()
// 42
cons(42).next()
// undefined

// zero-copy concat (supporting nullable parts/sub-sequences)
[...iterator(concat(null, aseq([]), aseq([1, 2]), undefined, aseq([3])))]
[...iterator(concat(null, aseq([]), aseq([1, 2]), undefined, cons(3)))]
// [ 1, 2, 3 ]

// if only arrays are used as sources, can also use concatA()
[...iterator(concatA(null, [], [1, 2], undefined, [3]))]
// [ 1, 2, 3 ]
```

### ISeqable support

Since the entire approach is interface based, sequences can be defined
for any custom datatype (preferably via the
[ISeqable](https://github.com/thi-ng/umbrella/blob/master/packages/api/src/api/seq.ts#L35)
Expand All @@ -123,6 +135,58 @@ import { dcons } from "@thi.ng/dcons";
// [ 3, 2, 1, 4, 5, 6 ]
```

### Lazy sequences

Lazily instantiated (possibly infinite) sequences can be created via
`lazyseq()`. This function returns an `ISeq` which only realizes its
values when they're requested.

```ts
import { defmultiN } from "@thi.ng/defmulti";

// defmultiN only used here to define multiple arities for `fib`
const fib = defmultiN({
0: () => fib(0, 1),
2: (a, b) => lazyseq(() => {
console.log(`realize: ${a}`);
return cons(a, fib(b, a + b));
})
});

fib()
// { first: [Function: first], next: [Function: next] }

fib().first()
// realize: 0
// 0

fib().next().first()
// realize: 0
// realize: 1
// 1

fib().next().next().first()
// realize: 0
// realize: 1
// realize: 1
// 1

fib().next().next().next().first()
// realize: 0
// realize: 1
// realize: 1
// realize: 2
// 2

fib().next().next().next().next().first()
// realize: 0
// realize: 1
// realize: 1
// realize: 2
// realize: 3
// 3
```

## Authors

${authors}
Expand Down
16 changes: 16 additions & 0 deletions packages/seq/src/cons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ISeq } from "@thi.ng/api";

/**
* Returns a new `ISeq` with `head` prepended to (optional) `seq`.
*
* @param head
* @param seq
*/
export const cons = <T>(head: T, seq?: ISeq<T>): ISeq<T> => ({
first() {
return head;
},
next() {
return seq;
}
});
2 changes: 2 additions & 0 deletions packages/seq/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export * from "./array";
export * from "./concat";
export * from "./cons";
export * from "./ensure";
export * from "./iterator";
export * from "./lazyseq";
39 changes: 39 additions & 0 deletions packages/seq/src/lazyseq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { Fn0, ISeq } from "@thi.ng/api";

/**
* Returns a new lazily evaluated `ISeq` produced by given function
* `fn`, which is only realized when values are requested. The function
* is only called once (if at all) and its result cached.
*
* @example
* ```ts
* const rnd = () => lazyseq(() => cons(Math.random(), rnd()));
* const a = rnd();
*
* a.first();
* // 0.4421468479982633
* a.next().first();
* // 0.29578903713266524
* ```
*
* @param fn
*/
export const lazyseq = <T>(fn: Fn0<ISeq<T> | undefined>): ISeq<T> => {
let called = false;
let seq: ISeq<T> | undefined;
const ensure = () => {
if (!called) {
seq = fn();
called = true;
}
return seq;
};
return {
first() {
return ensure() !== undefined ? seq!.first() : undefined;
},
next() {
return ensure() !== undefined ? seq!.next() : undefined;
}
};
};
21 changes: 21 additions & 0 deletions packages/seq/test/cons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as assert from "assert";
import { cons } from "../src";

describe("cons", () => {
it("cons", () => {
assert.equal(cons(1).first(), 1);
assert.equal(cons(1).next(), undefined);
assert.equal(
cons(1, cons(2))
.next()!
.first(),
2
);
assert.equal(
cons(1, cons(2))
.next()!
.next(),
undefined
);
});
});
21 changes: 21 additions & 0 deletions packages/seq/test/lazyseq.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ISeq } from "@thi.ng/api";
import * as assert from "assert";
import { cons, lazyseq } from "../src";

describe("lazyseq", () => {
it("lazyseq", () => {
const fib = (a?: number, b?: number): ISeq<number> =>
a !== undefined
? lazyseq(() => cons(a, fib(b, a + b!)))
: fib(0, 1);
assert.equal(fib().first(), 0);
// prettier-ignore
assert.equal(fib().next()!.first(), 1);
// prettier-ignore
assert.equal(fib().next()!.next()!.first(), 1);
// prettier-ignore
assert.equal(fib().next()!.next()!.next()!.first(), 2);
// prettier-ignore
assert.equal(fib().next()!.next()!.next()!.next()!.first(), 3);
});
});

0 comments on commit d25584e

Please sign in to comment.