Easier to compose with other objects, but harder to compose with other functions. For example:
const compose = (...f) => f.reduceRight((f, g) => (...x) => f(g(...x)));
const add = x => y => y + x;
const mul = x => y => y * x;
const div = x => y => y / x;
// (6x + 4) / 2
const complexOp = compose(mul(6), add(4), div(2));
// should return 50
complexOp(16);
All joking aside, you would achieve composition the same way, except using interfaces.
interface Operation<T> {
apply(input: T);
}
class Addition implements Operation<Number> {
}
class Multiplication implements Operation<Number> {
}
class Division implements Operation<Number> {
}
function compose(Operation<T>... operation, T input) {
T last = input;
for (Operation<T> op : operations) {
last = op.apply(last);
}
return last;
}
Of course, the above code is slightly verbose, but even if you could remove all the boiler plate it still doesn't look like good imperative code. This is what confuses me about currying: when translated to the imperative equivalent, it looks terrible.