function addToEach(add) {
return this.map(val => val + add);
}
function sum() {
return this.reduce((a, b) => a + b);
}
const result = [1, 2, 3]::addToEach(10)::sum();
const addToEach = add => arr => arr.map(val => val + add);
const sum = arr => arr.reduce((a, b) => a + b);
const result = [1, 2, 3] |> addToEach(10) |> sum;
// Complexity: what is `this`?
// Answer: Like all other instances of `this` in JS, it's the context object, or the global,
// unless it's been explicitly set by call/apply/bind.
function addToEach(add) {
return this.map(val => val + add);
}
function sum() {
return this.reduce((a, b) => a + b);
}
// Complexity: With ::, what is relation between the left and right hand side?
// Answer: It's sugar for func.call, so foo::bar(10) desugars to bar.call(foo, 10).
// This means bar is called with argument 10, and within it `this` is set to foo.
const result = [1, 2, 3]::addToEach(10)::sum();
The complexities here already exist in JS, so you may already know them. If not, learning them will come in handy with other JavaScript patterns.
// Complexity: What's with the two instances of =>?
// Answer: It's a function that returns a function. That'll become clear(ish) later.
// Complexity: Why is the subject (arr) after the thing that'll happen to it (add)?
// Answer: It just has to be backwards to work (despite pipeline aiming to solve this
// same problem with function nesting).
const addToEach = add => arr => arr.map(val => val + add);
// Complexity: Why doesn't this have two =>?
// Answer: Because it doesn't have args, it doesn't need a function within a function.
const sum = arr => arr.reduce((a, b) => a + b);
// Complexity: With |>, what is relation between the left and right hand side?
// Answer: The right-hand side is called with the left-hand side as its single argument.
// Complexity: Why is addToEach called as a function, whereas sum is just passed as a value?
// Answer: The left-hand side is called with the right, so addToEach(10) is a function that
// returns a function, whereas sum doesn't return a function so you just use its value.
const result = [1, 2, 3] |> addToEach(10) |> sum;
Although the character count is lower, the complexity is higher & pipeline-specific.
You can use other instance methods directly:
const { map, sort } = Array.prototype;
const headings = document.querySelectorAll('a')
::map(el => el.textContent)
::sort();
This is because instance methods already use this
.
Since it's a functional pattern, you can implement it as a function:
const pipe = (val, ...funcs) => funcs.reduce((val, func) => func(val), val);
const result = pipe([1, 2, 3], addToEach(10), sum);
@jakearchibald
Using jschoi's syntax makes the pipeline operator much simpler. (I agree that the "basic" pipeline operator doesn't really pay for itself - what it gains in conceptual simplicity it loses, badly, in practical simplicity.)
The only differences from your call operator example is that the functions take their argument directly, rather than implicitly via
this
, and then when callingaddToEach
you have to indicate that argument explicitly. (You can skip it withsum
as it's just a single name taking the piped value as its sole argument, thus satisfying the constraints of the "bare syntax", but you could also write that as|> sum(#)
if you wanted; whatever's more readable at that moment.)I agree that pulling methods out of prototypes or modules and then calling them on arbitrary objects is a pretty good benefit, and I think it might justify the call operator on its own. Using
.bind
/.call
/.apply
is sufficiently painful it's worthwhile to bake this into syntax imo. The call operator only actually solves.call
(and.apply
with spread syntax) tho;.bind
is still left unsolved and annoying. jschoi's pipeline solves that with the additional+>
operator;arr.map(+>foo.bar)
==arr.map(x=>foo.bar(x))
.