Skip to content

Instantly share code, notes, and snippets.

@leidegre
Created April 8, 2021 11:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leidegre/56cf58309123431fbafbdf089bb70dd8 to your computer and use it in GitHub Desktop.
Save leidegre/56cf58309123431fbafbdf089bb70dd8 to your computer and use it in GitHub Desktop.
Lazy sequence that does composition via transducers (can be extended to do chunked stuff for async support)
/** Sentinel value. Signal break. */
const BREAK = Symbol("break")
type Break = typeof BREAK
function isBreak<Result>(result: Result | Break): result is Break {
return result === BREAK
}
/** Sentinel value. Signal continue. */
const CONTINUE = Symbol("continue")
type Continue = typeof CONTINUE
function isContinue<Result>(result: Result | Continue): result is Continue {
return result === CONTINUE
}
// ---
/**
* A reducing function suitable for use with our transducer protocol.
*
* The `Result` type is `unknown` until the reducing function is called.
*
* `Break` is a unique type used to break out of the reducing loop.
*/
interface reducer<T, Result = unknown> {
(result: Result, input: T): Result | Break
}
/**
* A function that transforms one reducing function to another.
*/
interface transform<A, B, Result = unknown> {
(reducer: reducer<B, Result>): reducer<A, Result>
}
// ---
/** Mapping transducer function. */
function map<A, B>(mapper: (a: A) => B): transform<A, B> {
return (reducer) => {
return (result, input) => {
return reducer(result, mapper(input))
}
}
}
/** Filtering transducer function. */
function filter<T>(predicate: (value: T) => boolean): transform<T, T> {
return (reducer) => {
return (result, input) => {
if (predicate(input)) {
return reducer(result, input)
}
return result
}
}
}
// ---
/** Identity transducer function. */
function identity<A, B>(a: A): B {
return (a as unknown) as B
}
function combine<A, B, C>(a: transform<A, B>, b: transform<B, C>): transform<A, C> {
// Silly optimization (one less transducer to allocate)
if (a === identity) {
return (b as unknown) as transform<A, C>
}
return reducer => a(b(reducer))
}
function bind<A, B, Result>(xf: transform<A, B>, reducer: reducer<B, Result>): reducer<A, Result> {
return (xf as transform<A, B, Result>)(reducer)
}
// ---
class _Seq<From, T = From> {
it: Iterable<From> // the type of the underlying iterable (source)
xf: transform<From, T> // a transformation from that type to some other type (the actual enumeration type, can be same)
constructor(it: Iterable<From>, xf: transform<From, T> = identity) {
this.it = it
this.xf = xf
}
map<M>(mapper: (value: T) => M) {
return new _Seq(this.it, combine(this.xf, map(mapper)))
}
filter(predicate: (value: T) => boolean) {
return new _Seq(this.it, combine(this.xf, filter(predicate)))
}
reduce<Result>(reducer: reducer<T, Result>, initialValue: Result) {
const f = bind(this.xf, reducer)
let result = initialValue
for (const input of this.it) {
const next = f(result, input)
if (isBreak(next)) {
break
}
result = next
}
return result
}
/** Eager evaluation. */
toArray() {
return this.reduce<Array<T>>((array, value) => (array.push(value), array), [])
}
/** Lazy evaluation. */
*[Symbol.iterator](): IterableIterator<T> {
const f = bind(this.xf, ({ }: T | Continue, input) => input)
for (const input of this.it) {
const next = f(CONTINUE, input)
if (isContinue(next)) {
continue
}
if (isBreak(next)) {
break
}
yield next
}
}
}
console.log(new _Seq([1, 2, 3]).map(x => x * x).toArray())
console.log([...new _Seq([1, 2, 3]).map(x => x * x)])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment