ES7 Proposal: The Pipeline Operator
github.com
github.com
function bindMap(fn) { return this.map(fn) }
[ 1, 2, 3 ]::bindMap(n => n + 1)
// standalone use: bindMap.call(arr, fn)
function pipelineMap(fn) { return arr => arr.map(fn) }
[ 1, 2, 3 ] |> pipelineMap(n => n + 1)
// standalone use: pipelineMap(fn)(arr)
(Also nice that it works in a very similar way to decorators, although they won't be interchangeable in most cases, with decorators working on class constructors and property descriptors, instead of just any value.)Presumably, at most one of these proposals will make it in :)
The main goal of all of these proposals is "left to right" composition when working with functions, and functions take parameters.
function double(str) {
return str+', '+str;
}
function ucase(str) {
return str.slice(0,1).toUpperCase()+ str.slice(1);
}
function exclamation(str) {
return str+"!";
}
var _="hello";
_ = double(_);
_ = ucase(_);
// _ = foo(_);
_ = exclamation(_);
console.log(_); // Hello, hello!
function pipe() {
var functions=[];
for(var i=0; i<arguments.length; i++) {
functions[i]=arguments[i];
}
return function pipe_call(arg) {
for(var i=0; i<functions.length; i++) {
arg=functions[i](arg);
}
return arg;
}
}
var _="hello";
_ =pipe(
double,
ucase,
// foo,
exclamation
)(_);
console.log(_); // Hello, hello!There's a lot more operators than |> that you would want. Clojure commonly uses ->, ->>, as->, etc. It should be done in a library if it is done at all.
Also, going full-on functional in javascript is going to end badly, javascript simply wasn't designed for it. If you go down this path in real projects, sooner or later you inevitably end up at clojurescript or whatever other real FP language suits.
I'm not a believer in the FP vs. OO religion, and have historically been skeptical of the bind operator. That said, I think that both composition via objects and composition via functions have their place.
The pipeline operator attempts to make composition via functions more readable (left-to-right instead of right-to-left) and unless you think composition via functions is a bad idea in JS in all cases, I'm not sure what "full-on functional" has to do with it.
I don't know much about this...
Check that the function only writes on local variables and only reads closure variables and only calls other pure functions.
In javascript, 1 === 1, and "foo" === "foo". However, [] !== [] and {} !== {}. This is because in Javascript, like in Java, there is a distinction between value types (essentially, simple primitive values like numbers and strings) and reference types (collections - arrays and objects). When you assign an array to a variable, you are actually pointing the variable to a reference to the array. As a mutable data structure, its identity is more important than its value: it is expected that you shall be calling push, etc, and thus changing its value in place.
In Clojure (for example), (= 1 1) and (= "foo" "foo") and (= [] []) and (= {} {}). Because Clojure's data structures are immutable, the idea of checking for identity is almost meaningless (although you can, with the identical? function). In an FP style, with generic but immutable data structures, we are more interested in value equality, since we are not concerned so much with objects with a long lifecycle over which the values of their fields may change.
Javascript is in an odd position - it has enough features for both traditional OO and functional programming styles to be possible, but the two do not easily co-exist. Compromises are necessary. So, if our program is composed of mostly pure functions operating on generic data structures, we must do a lot of defensive copying, and use library functions for deep equality, because the data structures are not designed for this usage.
This is not to say it's infeasible to program in this way in Javascript: it is, and I try to. But a language designed from the ground up for functional programming will point you more enthusiastically in that direction than JS does.
On topic: like the syntax!
var newScore = add(7, validateScore( double(person.score) ))
becomes var newScore = person.score
|> double
|> score => add(7, score)
|> validateScore
This is very Forth-like. Operands get pushed on the stack, and operators take from the stack and push results back. In Forth, there's no operator precedence and no parentheses; it's pure reverse Polish.Why not go all the way to pure RPN?
7 person score . double ValidateScore add newScore =
See how it simplifies the syntax? Of course, you have to know how many arguments each function takes.Do we really want to go down this route? It's all syntax; it doesn't add any capability. As an extension to an existing language, it's likely to produce a mess.
Mixing functional and imperative notations is a problem. LISP is all nested parentheses, and that bothered a lot of people. Forth eliminates all the parentheses, which bothered different people. Python was pure imperative, ("lambda" went in over major objections) and is gradually getting more functional features, but they don't fit the syntax well.
Seriously, this just seems like syntax sugar that just isn't needed. I get that you might not think it's elegant, but is anyone seriously troubled or confused by method3(method2(method1(str)))? How would the pipeline syntax help here?
It also encourages you to write methods that take a single argument and return a single value, or partially apply those that don't.
What happens is much easier to read if you know what the operator means.
The only way the old way has more clarity is in the "we are already familiar with it" way. In any other way, this method trumps it.
Javascript's beauty lies in its versatile simplicity. Can we just leave these sorts of things to systems programming languages like C++? I came to javascript because of its lack of cruft, but if libraries start adopting this then I'll be forced to put it in my code as well. If that happens then I'll probably leave for nim or clojure, though that's not preferable.
Example:
(defn do-stuff [params-map]
(->
(build-object params-map)
(do-thing-with-object)
(extract-thing)
(format-thing)))
I'm not sure why you'd choose Clojure to get away from things like |>. cout << "foo";Doesn't seem fair to shut down this proposal because you happen to write and work with imperative code, a style that Javascript and modern abstractions are moving away from.
In fact, the Javascript hack to get a pipeline-like style is to make some opaque wrapper object that you can chain method calls onto like `_.chain(a).b().c().d().value()` in Lodash, when all you wanted was a chain of function application.
`|>`, `<|`, composition operators, and friends would all be welcome complements to the Javascript code bases I've worked with in the last few years.
## RamdaJS `_.pipe(b,c,d)(a)` // left to right
# But why are we inventing so many ways of doing forward, backward function application and composition.
Aside: I've never found an application of lodash `_.chain` that couldn't be expressed in a better way.
attrs
|> bounded('age', 1, 100)
|> format('name', /^[a-z]$/i)
|> Person.insertIntoDatabase
This reminds me of those "Perl Poetry" code snippets from 2001.This seems to me to be a case of "just because we can, doesn't mean we should."
On top of that, there are also "transpiler folks": Ruby |> CoffeeScript, C#/F# |> TypeScript, etc...
The classical Javascript programmer might be the last of a dying breed.
Nested function do one thing on a chunk of data, then another, then another.
*nix pipelines apply all the filters at once and control the scheduling and lifetime of the filters.
Those are quite different concepts, and I think it's confusing to conflate the two. Plus every decent editor can handle paired parentheses.
> I bet Lispers would beg to differ. :D
Would they?I wrote and read threading macros all the time when I used Clojure. Turns out it cleans up code.
exclaim capitalize doubleSay
Sure, some of us has spent 20 years doing this so it's easy, but you can't deny that this is far nicer to read:
doubleSay capitalize exclaim
I'm a big fan of Fluent Interfaces, so I use this construct fairly often:
"hello".doubleSay().capitalize().exclaim()
_(array)
.compact()
.map(function(x) {
return x * x;
})
.filter(function(x) {
return x > 100;
}).value()
(example from underscore/lodash)The problem with this is to be able to add a custom function to this pipeline, you have to extend the prototype of the library with your own functions, or re-implement it.
Extending the prototype is considered bad practice for a lot of reasons, for example due to polluting code in other modules or upstream changes breaking your code.
If instead of chaining methods we chained functions we are left with this awkward syntax:
custom(filter(map(compact(array), function(x) {
return x * x;
}), function(x) {
return x > 100;
}));
it's much cleaner to write it like this: compact(arr)
|> map(function(x) {
return x * x;
})
|> filter(function(x) {
return x > 100;
})
|> custom
The linked page has a nice example and explanation about this concerning promises https://github.com/mindeavor/es-pipeline-operator#sample-usa...That's exactly what transducers try to solve, and fortunately they do exist for js (http://jlongster.com/Transducers.js--A-JavaScript-Library-fo...) where processing of individual elements is separated from plumbing, so you can add your own custom computation in the chain. It even has pretty good performances (http://jlongster.com/Transducers.js-Round-2-with-Benchmarks), all with the standard data structures !
Of course transducers are useful only when you want to transform data, not when you want to act on them in the chain.
Especially that it worked in both directions.
my-list = filter <| sort <| get-data1 - // With pipe: var result = "hello" |> doubleSay |> capitalize |> exclaim;
With compose:
var greet = compose(exclaim, capitalize, doubleSay);
greet("hello");
// or compose(exclaim, capitalize, doubleSay)("hello");
2 -
var person = { score: 25 }; // With pipe:
var newScore = person.score
|> double
|> score => add(7, score)
|> validateScore
// With compose and curry:
var newScore = compose(validateScore, curry(add(7), double);
newScore(person.score);
3 - Mixin paradigms // With compose and curry :
var newScore = Function.compose(validateScore, add.curry(7), double);
newScore(person.score);Can we have this in C# 7? :)
This syntax can be used anywhere as long as the types signatures of the functions are conducive to the chaining.
import { sortBy, filter, map } from 'array-like';
let names = document.querySelectorAll('.entry')
-> sortBy('title')
-> filter(entry => entry.getAttribute('data-url') in whitelist)
-> map(entry => entry.getAttribute('name');
I personally find this more readable than either the bind proposal (which looks too much like a property access, and not enough like a function call, to me) and the proposed pipeline operator, which doesn't have an immediate meaning to my eyes.The main counterargument to "do it like underscore" is that underscore still ends up being an object whose methods are added to a prototype, not imported.
The benefit of the "pipeline" or "bind" approach is that you get left-to-right composition while still importing the names you're using.
Really? have you never seen unix pipes?
var result = exclaim(capitalize(doubleSay("hello")));
ES7 Proposal: The Pipeline Operator var result = "hello"
|> doubleSay
|> capitalize
|> exclaim;
CoffeeScript result = exclaim capitalize doubleSay 'hello'
I'll take CoffeeScript. let names = document.querySelectorAll('.entry')
.sortBy('title')
.filter(entry => entry.getAttribute('data-url') in whitelist)
.map(entry => entry.getAttribute('name');(and of course 'sortBy' is nonstandard)
And I want it painted green ;-)
> ... which doesn't have an immediate meaning to my eyes.
It's a matter of background. F#, Julia and Livescript have identical operators, likely among others.
https://msdn.microsoft.com/en-us/library/dd233229.aspx#Ancho...
http://docs.julialang.org/en/release-0.4/stdlib/base/?highli...
I personally consider compound operators (asymmetrical) to be pretty risky (the do, indeed, depend on background), and I like '->' because it has a clear meaning outside of programming.
But you're right, this is a bikeshed.
I'd prefer either version of the pipeline operator to bind.
It looked like a fine submission.
52 points, an hour ago, and it lies on the third page...
This proposal is about function application.