const pipe = (fn,...fns) => (...args) => fns.reduce( (acc, f) => f(acc), fn(...args));
const compose = (...fns) => pipe(...fns.reverse());
const mapObject = f => obj => 
    Object.assign({}, ...Object.keys(obj).map(k => ({[k]: f(obj[k]) })));
const Const = x => ({value: x, map: () => Const(x)})
const Identity = x => ({value: x, map:f => Identity(f(x))})
const lens = (getter, setter) => toFunctorFn =>
    target => {
        console.log(toFunctorFn(getter(target)))
        
        const functor = toFunctorFn(getter(target))
        
        return functor.map(focus => setter(focus, target))
    };
const view = (lens, x) => lens(Const)(x).value
  // Using `Const` effectively ignores the setter function of the `lens`,
  // leaving the value returned by the getter function unmodified.
  
const over = (lens, f, x) => lens(y => Identity(f(y)))(x).value
  // The value returned by the getter function is first transformed with `f`,
  // then set as the value of an `Identity`. This is then mapped over with the
  // setter function of the lens.
const set = (lens, v, x) => over(lens, () => v, x)
const prop = name => lens(x => x[name], (a, x) => ({...x, [name]: a}))
const xLens = lens(x => x.x, (a, s) => {
    return {...s, x: a}
});
const yLens = prop('y')
console.log(
//   mapObject(x => x.length)({a: [], b: [1,2,3,]})
    'view', view(xLens, {x: 12, y: 32})
)
console.log(
    'over', over(xLens, x => x * 2, {x: 12, y: 32})    
)
console.log(
    'set', set(xLens, 42, {x: 12, y: 32})    
)
console.log(
    'set', set(yLens, 42, {x: 12, y: 32})    
)
console.log(
    'set', set(pipe(prop('y'), prop('x')), 42, {x: {y: 71, z: 61}, w: 81})    
)