with ECMAScript
Septiembre 2015
@gruizdevilla
Meter y sacar de contenedores tiene un precio.
Y existe un límite a lo que podemos mover de una tacada.
La solución: cadena de montaje.
Transforma y reduce
const filter =
(fn, xs) =>
reduce(
(acc, x) => fn(x) ? acc.concat(x) : acc,
[],
xs
)
const map =
(fn, xs) =>
reduce(
(acc, x) => acc.concat(fn(x)),
[],
xs
)
const _curry =
(fn, oldArgs) =>
(...args) => {
let allArgs = [...oldArgs, ...args];
return allArgs.length >= fn.length ? fn(...allArgs)
: _curry(fn, allArgs);
}
const curry = fn => _curry(fn, []);
Componiendo transformaciones
const sequence =
(fn1,...fns) =>
(...args) => reduce(
(acc, fn) => fn(acc),
fn1(args),
fns
)
const transf = sequence(filter(greaterThan2), map(add2), take(3));
let result = transf([1,2,3,4,5,6]);
[1, 2, 3, 4, 5, 6] -> [3, 4, 5, 6] -> [5, 6, 7, 8] -> [5, 6, 7]
Pasito a pasito, que ya nos lanzaremos
Despachamos a la implementación del objeto, si está disponible
const _reduce =
(fn, acc, [x, ...xs]) =>
x === undefined ? acc : reduce(fn, fn(acc, x), xs);
const reduce =
(fn, acc, xs) =>
hasMethod('reduce', xs) ? xs.reduce(fn, acc)
: _reduce(fn, acc, xs);
Por ejemplo, con una Lista
class Cons {
constructor(head, tail){
this.head = head;
this.tail = tail;
}
reduce(fn, acc){
return this.tail.reduce(fn, fn(acc, this.head))
}
}
const Nil = {
reduce: (fn, acc) => acc
}
const cons = (head, tail) => new Cons(head, tail);
const list = cons(1, cons(2, cons(3, Nil)));
console.log(reduce(add, 0, list)); //returns 6
const filter =
(fn, xs) =>
reduce(
(acc, x) => fn(x) ? acc.concat(x) : acc,
[],
xs
)
const map =
(fn, xs) =>
reduce(
(acc, x) => acc.concat(fn(x)),
[],
xs
)
const map =
(fn) =>
(concat) =>
xs =>
reduce(
(acc, x) => concat(acc, fn(x)),
[],
xs
)
map(x => x * 10)(append)([1,2,3]); // [10, 20, 30]
const append =
(xs, x) =>
xs.concat(x);
Paso 1: agregando
const filter =
(fn) =>
(concat) =>
xs =>
reduce(
(acc, x) => fn(x) ? concat(acc, x) : acc,
[],
xs
)
filter(x => x < 3)(append)([1,2,3]) // [1, 2]
const append =
(xs, x) =>
xs.concat(x);
Al abstraernos de la concatenación ocurre esto:
const arr = [1,2,3];
const list = cons(1, cons(2, cons(3, Nil)));
const append = (acc, item) => acc.concat(item);
const appendList = (acc, item) => cons(item, acc);
const not2 = x => x != 2;
filter(not2)(append)(list); // [1, 3]
filter(not2)(appendList)(arr); //{"head":3,"tail":{"head":1,"tail":[]}}
Casi funciona, pero el valor inicial de acumulación es "[]" y debería haber sido Nil para la acumulación en lista.
Paso 2: saquemos la reducción y el valor inicial
const mapper =
fn =>
concat =>
(acc, item) =>
concat(acc, fn(item));
const list = cons(1, cons(2, cons(3, Nil)));
const arr = [1,2,3];
const identity = x => x;
reduce(mapper(identity)(append), [], list);
//[1,2,3]
reduce(mapper(identity)(appendList), Nil, arr);
//{"head":3,"tail":{"head":2,"tail":{"head":1,"tail":{}}}}
const list = cons(1, cons(2, cons(3, Nil)));
const arr = [1,2,3];
const identity = x => x;
reduce(mapper(identity)(append), [], list);
//[1,2,3]
reduce(mapper(identity)(appendList), Nil, arr);
//{"head":3,"tail":{"head":2,"tail":{"head":1,"tail":{}}}}
const mult10 = x => x * 10;
reduce(mapper(mult10)(append), [], list);
//[10,20,30]
const mapper =
fn =>
concat =>
(acc, item) =>
concat(acc, fn(item));
const filterer =
fn =>
concat =>
(acc, item) =>
fn(item) ? concat(acc, item) : acc;
const arr = [1,2,3];
reduce(filterer(not2)(appendList), Nil, arr);
//{"head":3,"tail":{"head":1,"tail":{}}}
reduce(
mapper(add1)(
mapper(mult10)
(append)
),
[],
list
)
//[20,30,40]
reduce(
filterer(not2)(
mapper(add1)(
mapper(mult10)
(append)
)
),
[],
list
)
//[20, 40]
sequence(f1, f2, f3)(x) = f3(f2(f1(x)));
(f1○f2○f3)(x) = compose(f1, f2, f3)(x) = f1(f2(f3(x)));
const sequence =
(fn1,...fns) =>
(...args) =>
reduce(
(acc, fn) => fn(acc),
fn1(...args),
fns
);
const compose =
(...args) =>
sequence(...args.reverse());
const transformation = compose(
filterer(not2),
mapper(add1),
mapper(mult10)
)
reduce(
transformation(append),
[],
list
)
//[20,40]
reduce(
transformation(appendList),
Nil,
arr
)
//{"head":40,"tail":{"head":20,"tail":Nil}}
reduce( // se encarga de reducir
transformation(append), // le decimos cual es la transformación
// y la forma de reducir
[], // le decimos el valor inicial
list // le decimos cual es la fuente de datos
)
const tranduce =
(transform, step, init, xs) =>
reduce(transform(step), init, xs);
const taker =
n =>
concat =>
(
(pos = 0) =>
(acc, item) => (pos++, pos <= n ? concat(acc, item) : acc)
)()
Una función de grado/aridad uno, que recibe un tranformer para poder componer.
const map =
fn =>
xf =>
new Mapper(fn, xf);
const map =
fn =>
xf =>
isTransformer(xf) ? new Mapper(fn, xf)
: _plainOldMap(fn, xf);
Pero para ello necesitamos saber si "xf" es un transformer.
class Mapper extends Base {
constructor(fn, xf) {
super();
this.xf = xf;
this.f = fn;
}
'@@transducer/step'(result, input) {
return this.xf['@@transducer/step'](result, this.f(input));
}
'@@transducer/init'() {
return this.xf['@@transducer/init']();
}
'@@transducer/result'(result) {
return this.xf['@@transducer/result'](result);
}
}
const transformation = compose(
filter(isOdd),
map(addTen),
map(prop('somprop'))
);
transformation([{obj1},{obj2},{obj3},...])
into([], transformation ,someReducibleObject)
const arrayReduce =
(xf, acc, arr, pos, length) =>
acc && acc['@@transducer/reduced'] ? acc['@@transducer/value'] :
length === pos ? xf['@@transducer/result'](acc)
: arrayReduce(xf,
xf['@@transducer/step'](acc, arr[pos]),
arr,
pos + 1,
length);
//pagevisits.js
var lines = require('transduce/string/lines')
var stream = require('transduce-stream')
var parseLog = R.compose(
lines(),
R.filter(isPage),
R.map(splitLine),
R.map(valueToUrl),
R.map(R.join(' visited ')),
R.map(R.add(R.__, '\n')))
process.stdin.pipe(stream(parseLog)).pipe(process.stdout)
process.stdin.resume()
# Term 1
$ tail -f access.log | node pagevisits.js
127.0.0.1 visited http://localhost/path1/
127.0.0.1 visited http://localhost/path1/