Como encadear funções de mapa e filtro na ordem correta

Eu realmente gosto de encadearArray.prototype.map, filter ereduce para definir uma transformação de dados. Infelizmente, em um projeto recente que envolvia grandes arquivos de log, eu não conseguia mais fazer um loop nos meus dados várias vezes ...

Meu gol:

Eu quero criar uma função que encadeia.filter e.map métodos, em vez de mapear sobre uma matriz imediatamente, compondo uma função que faz um loop nos dadosuma vez. Ou seja:

const DataTransformation = () => ({ 
    map: fn => (/* ... */), 
    filter: fn => (/* ... */), 
    run: arr => (/* ... */)
});

const someTransformation = DataTransformation()
    .map(x => x + 1)
    .filter(x => x > 3)
    .map(x => x / 2);

// returns [ 2, 2.5 ] without creating [ 2, 3, 4, 5] and [4, 5] in between
const myData = someTransformation.run([ 1, 2, 3, 4]); 
Minha tentativa:

Inspirado poresta resposta eeste blogpost Comecei a escrever umTransduce função.

const filterer = pred => reducer => (acc, x) =>
    pred(x) ? reducer(acc, x) : acc;

const mapper = map => reducer => (acc, x) =>
    reducer(acc, map(x));

const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
    map: map => Transduce(mapper(map)(reducer)),
    filter: pred => Transduce(filterer(pred)(reducer)),
    run: arr => arr.reduce(reducer, [])
});
O problema:

O problema com oTransduce O snippet acima é que ele roda "para trás" ... O último método que eu encadeio é o primeiro a ser executado:

const someTransformation = Transduce()
    .map(x => x + 1)
    .filter(x => x > 3)
    .map(x => x / 2);

// Instead of [ 2, 2.5 ] this returns []
//  starts with (x / 2)       -> [0.5, 1, 1.5, 2] 
//  then filters (x < 3)      -> [] 
const myData = someTransformation.run([ 1, 2, 3, 4]);

Ou, em termos mais abstratos:

Vá de:

Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, f(g(x)))

Para:

Transducer(concat).map(f).map(g) == (acc, x) => concat(acc, g(f(x)))

O que é semelhante a:

mapper(f) (mapper(g) (concat))

eu acho que entendiporque isso acontece, mas não consigo descobrir como corrigi-lo sem alterar a "interface" da minha função.

A questão:

Como posso fazer meuTransduce cadeia de métodofilter emap operações na ordem correta?

Notas:Estou apenas aprendendo sobre o nome de algumas das coisas que estou tentando fazer. Informe-me se usei incorretamente oTransduce prazo ou se existem maneiras melhores de descrever o problema.Estou ciente de que posso fazer o mesmo usando um aninhadofor ciclo:

const push = (acc, x) => (acc.push(x), acc);
const ActionChain = (actions = []) => {
  const run = arr =>
    arr.reduce((acc, x) => {
      for (let i = 0, action; i < actions.length; i += 1) {
        action = actions[i];

        if (action.type === "FILTER") {
          if (action.fn(x)) {
            continue;
          }

          return acc;
        } else if (action.type === "MAP") {
          x = action.fn(x);
        }
      }

      acc.push(x);
      return acc;
    }, []);

  const addAction = type => fn => 
    ActionChain(push(actions, { type, fn }));

  return {
    map: addAction("MAP"),
    filter: addAction("FILTER"),
    run
  };
};

// Compare to regular chain to check if 
// there's a performance gain
// Admittedly, in this example, it's quite small...
const naiveApproach = {
  run: arr =>
    arr
      .map(x => x + 3)
      .filter(x => x % 3 === 0)
      .map(x => x / 3)
      .filter(x => x < 40)
};

const actionChain = ActionChain()
  .map(x => x + 3)
  .filter(x => x % 3 === 0)
  .map(x => x / 3)
  .filter(x => x < 40)


const testData = Array.from(Array(100000), (x, i) => i);

console.time("naive");
const result1 = naiveApproach.run(testData);
console.timeEnd("naive");

console.time("chain");
const result2 = actionChain.run(testData);
console.timeEnd("chain");
console.log("equal:", JSON.stringify(result1) === JSON.stringify(result2));

Aqui está minha tentativa em um snippet de pilha:

const filterer = pred => reducer => (acc, x) =>
  pred(x) ? reducer(acc, x) : acc;

const mapper = map => reducer => (acc, x) => reducer(acc, map(x));

const Transduce = (reducer = (acc, x) => (acc.push(x), acc)) => ({
  map: map => Transduce(mapper(map)(reducer)),
  filter: pred => Transduce(filterer(pred)(reducer)),
  run: arr => arr.reduce(reducer, [])
});

const sameDataTransformation = Transduce()
  .map(x => x + 5)
  .filter(x => x % 2 === 0)
  .map(x => x / 2)
  .filter(x => x < 4);
  
// It's backwards:
// [-1, 0, 1, 2, 3]
// [-0.5, 0, 0.5, 1, 1.5]
// [0]
// [5]
console.log(sameDataTransformation.run([-1, 0, 1, 2, 3, 4, 5]));

questionAnswers(2)

yourAnswerToTheQuestion