"use strict";        4

chapter("Hi-order functions");
section("Minimum by absolute value");

let minimumByAbs = function(...args) {
    let result = Infinity;
    for (const arg of args) {
        if (Math.abs(result) > Math.abs(arg)) {
            result = arg;
        }
    }
    return result;
};
println("minimumByAbs =", minimumByAbs);
example("minimumByAbs(1, -2, 3)");


section("Unify minimum and minimumByAbs");

subsection("High-order functions");
function minimumBy(comparator, init = Infinity) {
    return (...args) => {
        let result = init;
        for (const arg of args) {
            if (comparator(result, arg) > 0) {
                result = arg;
            }
        }
        return result;
    }
}
println(minimumBy);

function comparing(f) {
    return (a, b) => f(a) - f(b);
}
println(comparing);

const identity = a => a;
println("const identity =", identity);

function maximumBy(comparator, init = -Infinity) {
    return minimumBy((a, b) => -comparator(a, b), init);
}
println(maximumBy);


subsection("Definitions");

let minimumByV = minimumBy(comparing(identity));
minimumByAbs = minimumBy(comparing(Math.abs));
let maximumByLength = maximumBy(comparing(s => s.length), "");

example("minimumByV");
example("minimumByAbs");
example("maximumByLength");
example("minimumByV(1, -2, 3)");
example("minimumByAbs(1, -2, 3)");
example("maximumByLength('aa', 'bbb', 'c')");


section("Unify minimumBy and sum");

subsection("High-order functions");
function foldLeft(f, zero) {
    return (...args) => {
        let result = zero;
        for (const arg of args) {
            result = f(result, arg);
        }
        return result;
    }
}
println(foldLeft);

function minBy(f) {
    return (a, b) => f(a) < f(b) ? a : b;
}
println(minBy);

subsection("Definitions");
const sum2 = foldLeft((a, b) => a + b, 0);
const product = foldLeft((a, b) => a * b, 1);
minimumByAbs = foldLeft(minBy(comparing(Math.abs)), Infinity);
example("sum2(1, -2, 3)");
example("product(1, -2, 3)");
example("minimumByAbs(1, -2, 3)");


section("sumSquares and sumAbs");

let square = x => x * x;
let sumSquares = foldLeft((a, b) => a + square(b), 0);
let sumAbs = foldLeft((a, b) => a + Math.abs(b), 0);
example("sumSquares(1, -2, 3)");
example("sumAbs(1, -2, 3)");


subsection("High-order functions");
function map(f) {
    return (...args) => {
        const result = [];
        for (const arg of args) {
            result.push(f(arg));
        }
        return result;
    }
}
println(map);

function compose(f, g) {
    return (...args) => f(g(...args));
}
println(compose);

function unspread(f) {
    return args => f(...args);
}
println(unspread);

subsection("Definitions");
sumSquares = compose(unspread(sum2), map(square));
sumAbs = compose(unspread(sum2), map(Math.abs));
example("sumSquares(1, -2, 3)");
example("sumAbs(1, -2, 3)");


section("diff");

let diff = dx => f => x => (f(x + dx) - f(x - dx)) / 2 / dx;

let dsin = diff(1e-7)(Math.sin);
for (let i = 0; i < 10; i++) {
    println(i + " " + Math.cos(i) + " " + dsin(i) + " " + Math.abs(Math.cos(i) - dsin(i)));
}


section("Currying");
subsection("curry");

const curry = f => a => b => f(a, b);

const addC = curry((a, b) => a + b);
const add10 = addC(10);
example("addC(10)(20)");
example("add10(20)");


subsection("uncurry");

const uncurry = f => (a, b) => f(a)(b);
const addU = uncurry(a => b => a + b);
example("addU(10, 20)");


subsection("mCurry");

println("bind");
let bind = (f, ...as) => (...args) => f(...[...as, ...args]);
let add100 = bind((a, b) => a + b, 100);
example("    add100(200)");

println("mCurry");
let mCurry = curry(bind);
let sub = mCurry((a, b, c) => a - b - c);
let sub10 = sub(10);
example("    sub(10)(20, 30)");
example("    sub10(20, 30)");
