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);
The
bind
operator proposal works great forprototype
methods, but what about non-prototype methods which the pipeline operator solves? For example, a utility library.Say I have a utility library which exports various methods. The following will work fine with the
bind
operator proposal:But what about the following?
The code stops working depending on the way we import the utilities because
this
will now refer to module context. Granted this is a nuance of howthis
works, but I think it's safe to assume that it may not be immediately obvious and potentially confusing. There is no way to prevent such invalid usage either.Agreed that developers should be familiar with
this
because it already exists, but it doesn't mean thatthis
is very approachable. I spent a significant time understanding the nuances ofthis
when learning, but I still run into mistakes time to time involvingthis
even if I understand how it works.I'm just a random developer, I'm not even an "FP folk". I find the pipeline operator easier to use. Sure to use them with existing prototype methods, it's a bit more work when declaring the utility functions (
const map = (...args) => arr => Array.prototype.map.apply(arr, args)
), but you define them only once and use them several times.Also regarding complexity part, you mention that the complexity of
this
already exists in JavaScript, so we should learn it, however regarding pipeline's complexity, a complaint is the usage of closures. Closures also exist in JavaScript, so why prefer the complexity ofthis
over the complexity of closures? One distinction to make is thatthis
shifts the complexity towards the call site, whereas closures shift the complexity to the declaration site.That's unfair. You need to use a non-arrow function with the
bind
operator, but it's fine to use an arrow function withpipeline
operator. Examples should represent how people will use it in the real world, and I'd never use a normal function over an arrow function at least in the anonymous function you have there.