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);
I really dislike all that placeholder
(1, ?)
syntax - it looks like a SQL prepared statement and sort of feels like a new type of "binding". It's like a bind, because what you're making when you sayfoo(1, ?)
is a new function to pass back, but since you have parens in it it looks like it's a function call (which it's not). I don't love filling my pipes up with arrow functions, but it's readable and consistent.Ultimately this becomes a debate between binding a context, and currying, so it makes no sense to compromise with the placeholders and re-introduce a pseudo-binder.
Nobody's really "right" in an absolute sense between which should be used, because it's really a question of values. If you're looking backwards, and thinking in terms of previous patterns, the
::
wins hands-down. Being able to code like this is beautiful:but, this only works for things that follow the
this
approach. This is especially clunky when working with collections you hope will work like Arrays in their context but don't, e.g.My favourite thing with
::
is its behaviour when prefixed, so::console.log
isconsole.log.bind(console)
. This is something I see popping up constantly - pretty much anywhere I pass in a function into an event handler.If we're looking forward, even with
class
and everything, the concept ofthis
seems to be less popular every year. Curried, context-free functions are easy to reason about, test, etc. and gaining in use.Personally I'm on team
::bind
right now, but maybe I'll have a different opinion next year. Especially when dealing with async types -- Promises and Observables -- passing along a return as a single argument in a pipe is pretty normal. If your one arg is theObservable
, worrying about placeholders is moot. Pipes are built to carry streams, after all.You could have almost identical syntax with
::
, but reads backwards to me. The pipe's saying "take these results and pass them along" (makes sense for async results), but the::
is saying "take this observable, and give it a method it can run".