Skip to content

Instantly share code, notes, and snippets.

@JamieMason
Last active October 11, 2019 14:59
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JamieMason/84a9e3372bc899e416c119af500a0ccf to your computer and use it in GitHub Desktop.
Save JamieMason/84a9e3372bc899e416c119af500a0ccf to your computer and use it in GitHub Desktop.
Learning about JavaScript Transducers

Learning about JavaScript Transducers

Work in progress sketch based on following along with the video course at https://egghead.io/courses/quickly-transform-data-with-transducers.

Source

const id = (value) => value;
const isArray = (value) => Array.isArray(value);
const isNumber = (value) => typeof value === 'number';
const isObject = (value) => Object.prototype.toString.call(value) === '[object Object]';

const compose = (...fns) => fns.reduce((prevFn, nextFn) => (...args) => nextFn(prevFn(...args)), id);
const map = (mapper) => (reducer) => (acc, value) => reducer(acc, mapper(value));
const filter = (isNeeded) => (reducer) => (acc, value) => (isNeeded(value) ? reducer(acc, value) : acc);

const reduceAddable = (value, nextValue) => value + nextValue;
const reduceObject = (obj, value) => Object.assign(obj, value);
const reduceArray = (array, value) => array.concat(value);

const transduce = (mapper, reducer, initialValue, iterable) => {
  let acc = initialValue;
  const transformedReducer = mapper(reducer);
  if (isObject(iterable)) {
    for (const key in iterable) {
      acc = transformedReducer(acc, [key, iterable[key]]);
    }
  } else {
    for (const value of iterable) {
      acc = transformedReducer(acc, value);
    }
  }
  return acc;
};

const into = (mapper, initialValue, iterable) => {
  if (isArray(initialValue)) return transduce(mapper, reduceArray, initialValue, iterable);
  if (isObject(initialValue)) return transduce(mapper, reduceObject, initialValue, iterable);
  throw new Error('into: only arrays and objects as `initialValue` are supported');
};

const seq = (mapper, iterable) => {
  if (isArray(iterable)) return transduce(mapper, reduceArray, [], iterable);
  if (isObject(value)) return transduce(mapper, reduceObject, {}, iterable);
  throw new Error('seq: Unsupported collection type');
};

Usage

const filterIsEven = filter((a) => a % 2 === 0);
const mapDouble = map((a) => a * 2);
const mapToObject = map((value) => ({ [value]: value }));

const withDoubledEvenNumber = compose(
  mapDouble,
  filterIsEven
);

const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];

const resultAsArray1 = numbers.reduce(withDoubledEvenNumber(reduceArray), []);
const resultAsArray2 = transduce(withDoubledEvenNumber, reduceArray, [], numbers);
const resultAsArray3 = into(withDoubledEvenNumber, [], numbers);
const resultAsArray4 = seq(withDoubledEvenNumber, numbers);

numbers.forEach((number) => {
  console.log('withDoubledEvenNumber([], %s) === %s', number, withDoubledEvenNumber(reduceArray)([], number));
});
console.log('resultAsArray1', resultAsArray1);
console.log('resultAsArray2', resultAsArray2);
console.log('resultAsArray3', resultAsArray3);
console.log('resultAsArray4', resultAsArray4);

const withDoubledEvenNumberAsObject = compose(
  mapToObject,
  withDoubledEvenNumber
);

const resultAsObject1 = numbers.reduce(withDoubledEvenNumberAsObject(reduceObject), {});
const resultAsObject2 = transduce(withDoubledEvenNumberAsObject, reduceObject, {}, numbers);
const resultAsObject3 = into(withDoubledEvenNumberAsObject, {}, numbers);

numbers.forEach((number) => {
  console.log(
    'withDoubledEvenNumberAsObject({}, %s) === %s',
    number,
    withDoubledEvenNumberAsObject(reduceObject)({}, number)
  );
});

console.log('resultAsObject1', resultAsObject1);
console.log('resultAsObject2', resultAsObject2);
console.log('resultAsObject3', resultAsObject3);

Output

withDoubledEvenNumber([], 1) ===
withDoubledEvenNumber([], 2) === 4
withDoubledEvenNumber([], 3) ===
withDoubledEvenNumber([], 4) === 8
withDoubledEvenNumber([], 5) ===
withDoubledEvenNumber([], 6) === 12
withDoubledEvenNumber([], 7) ===
withDoubledEvenNumber([], 8) === 16
withDoubledEvenNumber([], 9) ===
resultAsArray1 [ 4, 8, 12, 16 ]
resultAsArray2 [ 4, 8, 12, 16 ]
resultAsArray3 [ 4, 8, 12, 16 ]
resultAsArray4 [ 4, 8, 12, 16 ]
withDoubledEvenNumberAsObject({}, 1) === {}
withDoubledEvenNumberAsObject({}, 2) === {"4":4}
withDoubledEvenNumberAsObject({}, 3) === {}
withDoubledEvenNumberAsObject({}, 4) === {"8":8}
withDoubledEvenNumberAsObject({}, 5) === {}
withDoubledEvenNumberAsObject({}, 6) === {"12":12}
withDoubledEvenNumberAsObject({}, 7) === {}
withDoubledEvenNumberAsObject({}, 8) === {"16":16}
withDoubledEvenNumberAsObject({}, 9) === {}
resultAsObject1 { '4': 4, '8': 8, '12': 12, '16': 16 }
resultAsObject2 { '4': 4, '8': 8, '12': 12, '16': 16 }
resultAsObject3 { '4': 4, '8': 8, '12': 12, '16': 16 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment