Created
August 1, 2017 16:39
-
-
Save lilactown/bfcbad9736d2ed1783d735d44b330fae to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Reduced<Value> = { value: Value, completed: boolean }; | |
type Reducer<Accumulator, Input> = (whatever: Accumulator, input: Input) => Accumulator | Reduced<Accumulator>; | |
type Transducer<Input, Output> = | |
<Accumulator>(reducer: Reducer<Accumulator, Output>) => Reducer<Accumulator, Input>; | |
// utils | |
function reduced<Value>(value: any, completed: boolean): Reduced<Value> { | |
return { value, completed }; | |
} | |
function isReduced<Value>(v: any): v is Reduced<Value> { | |
return v && v.value && v.completed !== undefined; | |
} | |
// transducers | |
function map<Input, Output>(f: (x: Input) => Output): Transducer<Input, Output> { | |
return function mapTransducer<Accumulator>(reducer: Reducer<Accumulator, Output>): Reducer<Accumulator, Input> { | |
return function mapReducer(whatever, input) { | |
return reducer(whatever, f(input)); | |
} | |
} | |
} | |
function filter<Input>(p: (x: Input) => boolean): Transducer<Input, Input> { | |
return function filterTransducer<Accumulator>(reducer: Reducer<Accumulator, Input>): Reducer<Accumulator, Input> { | |
return function filterReducer(whatever, input) { | |
return p(input) ? reducer(whatever, input) : whatever; | |
} | |
} | |
} | |
// BROKEN | |
function concat<Accumulator, Input>(reducer: Reducer<Accumulator, Input>): Reducer<Accumulator, Input> { | |
return function concatReducer(whatever, input) { | |
return reducer(whatever, input); | |
} | |
} | |
// function comp<T extends Transducer<any, any>>(...xfs: T[]): T | |
function compose(...xfs: Transducer<any, any>[]): Transducer<any, any> { | |
return function compTransducer(reducer) { | |
return xfs.reverse().reduce((xform, xf) => xf(xform), reducer); | |
} | |
} | |
function mapCat<Input, Output>(f: (x: Input) => Output[]): Transducer<Input, Output> { | |
return compose(map(f), concat); | |
} | |
function take<Value>(n: number) { | |
return function takeTransducer<Accumulator>(reducer: Reducer<Accumulator, Value>): Reducer<Accumulator, Value> { | |
let count = 0; | |
return function takeReducer(whatever, input) { | |
if (count < n) { | |
count++; | |
return reducer(whatever, input); | |
} | |
return reduced(whatever, true); | |
} | |
} | |
} | |
// do transduction | |
function transduce<Input, Output, T extends Transducer<Input, any>>( | |
xf: T, | |
f: Reducer<Output, any>, | |
init: Output, | |
coll: Iterable<Input> | |
) { | |
const reducer = xf(f); | |
let accumulator = init; | |
for (const el of coll) { | |
const reducedVal = reducer(accumulator, el); | |
// console.log(el, reducedVal); | |
if (isReduced<Output>(reducedVal)) { | |
if (reducedVal.completed) { | |
return reducedVal.value; | |
} | |
} | |
else { | |
accumulator = reducedVal; | |
} | |
} | |
return accumulator; | |
} | |
function toArray<Input, Output>(xf: Transducer<Input, Output>, coll: Iterable<Input>): Output[] { | |
return transduce(xf, (acc, x) => acc.concat([x]), new Array<Output>(), coll); | |
} | |
function toFn<Input, Output, Accumulator>( | |
xf: Transducer<Input, Output>, | |
f: Reducer<Accumulator, Output>, | |
// showReduced: boolean = false | |
): Reducer<Accumulator, Input> { | |
const reducer = xf(f); | |
return (acc, x) => { | |
const val = reducer(acc, x); | |
if (isReduced<Accumulator>(val)) { | |
return val.value; | |
} | |
return val; | |
}; | |
} | |
function* toIter<Input, Output>(xf: Transducer<Input, Output>, coll: Iterable<Input>): IterableIterator<Output> { | |
const reducer = xf((_acc: Output, x) => x); | |
/** | |
* Transforms like `filter` return the accumulator value | |
*/ | |
const DUMMY_VAL= <Output>{}; | |
const iter = coll[Symbol.iterator](); | |
let currentEl = iter.next(); | |
while (!currentEl.done) { | |
let value = reducer(DUMMY_VAL, currentEl.value); | |
if (isReduced<Output>(value)) { | |
if (value.completed) { | |
return value.value; | |
} | |
} | |
else { | |
yield value; | |
} | |
currentEl = iter.next(); | |
} | |
} | |
// examples | |
const inc = map((x: number) => x + 1); | |
const isEven = filter((x: number) => x % 2 === 0); | |
const incEven = compose(inc, isEven); | |
const result = transduce( | |
compose(inc, take(5)), | |
(acc, x) => acc + x, | |
0, | |
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9] | |
); | |
console.log(result); | |
const resultArr = toArray(incEven, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); | |
console.log(resultArr); | |
const incArray = toFn( | |
inc, | |
(acc: any[], x) => acc.concat([x]) | |
); | |
const resultFn = [0,1,2,3,4,5,6,7,8,9].reduce(incArray, []); | |
console.log(resultFn); | |
const resultIter = toIter(inc, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); | |
for (const el of resultIter) { | |
console.log(el); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment