Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pipe Operator #4144

Closed
wants to merge 3 commits into from
Closed

Pipe Operator #4144

wants to merge 3 commits into from

Conversation

hzamani
Copy link

@hzamani hzamani commented Nov 12, 2015

Pipe operator |> adds its right hand expression as first argument to the left hand function invocation.

Selecting first operand was a smart choice in Elixir as they don't have function currying. I think it's also a good choice for CoffeeScript, because of JS libraries common structure on accepting "subject" (i.e. Object|Array|String|...) as first arg.

An answer to #1339 and #3600

_ = require 'lodash'

users = [
  { user: 'barney',  active: false, age: 37}
  { user: 'fred',    active: true,  age: 21}
  { user: 'pebbles', active: true,  age: 19}
]

youngest = users
  |> _.select('active')
  |> _.sortBy('age')
  |> _.first
  |> _.get('user')
  |> _.capitalize

console.log youngest

@lydell
Copy link
Collaborator

lydell commented Nov 12, 2015

Wow, what a simple and clean patch!

@aurium
Copy link
Contributor

aurium commented Nov 12, 2015

This looks nice to me. 👍

@vendethiel
Copy link
Collaborator

Pretty nice patch! Does it work with the pipe on the first line, and the call on the second line as well? (probably should "just work")

@hzamani
Copy link
Author

hzamani commented Nov 13, 2015

@vendethiel yes it works

f = (x,y) -> x + y
g = (x) -> x * 10
h = (x,y,z) -> [x,y,z]

10 |> f(1) |>
g
|> h(1,2)

will be [ 110, 1, 2 ]

@seanstrom
Copy link

@hzamani Will I be able to pipe a value to an inlined, partially applied function?

add = x -> y -> x + y
10 |> add(10)

@hzamani
Copy link
Author

hzamani commented Dec 9, 2015

@seanstrom No, pipe is just a syntactic sugar for adding parameters to a function call, 10 |> add(20) is same as add(10, 20) .

@jamiter
Copy link

jamiter commented Dec 14, 2015

👍 Looking good!

@zweifisch
Copy link

👍 one more reason to stick with coffeescript

@jamiter
Copy link

jamiter commented Dec 17, 2015

So what needs to be done to get this into CoffeeScript? 😄

@vendethiel
Copy link
Collaborator

@jashkenas 's approval

@davinov
Copy link

davinov commented Jan 16, 2016

Lodash already adresses this concern by _.chain or its short _ function. However, for many other libs that doesn't provide partial functions, that's a great idea! 👍

@taylorlapeyre
Copy link

Really looking forward to getting this really useful tool!

@zimt28
Copy link

zimt28 commented Feb 28, 2016

Really nice, what does @jashkenas think?

@blitmap
Copy link

blitmap commented Mar 20, 2016

Yes, please!

@dbackeus
Copy link

With functional programming styles on the rise this would be a very welcome addition to coffeescript.

Some debaters in previous discussions have insisted OO style method chaining provides equally nice syntax. However there are certainly situations where it's not possible or wanted to have those methods on the objects worked on.

A general trend can currently be seen where OO style is giving way for functional style so definitely time to rethink previous decisions. Adding pipes would enable nicer ways of dealing with more functional code designs.

@0chroma
Copy link

0chroma commented Apr 29, 2016

Having used this in Elixir, the pipe operator really encourages you to split things up into small functions and chain them together, it feels extremely satisfying and leads to some great code. Also leads to much simpler code compared to the OO-style chaining approach, since you're just returning the value instead of a scope that you need to hold onto in a closure (ie underscore). Would absolutely love to see this in Coffeescript.

@cullophid
Copy link

This would be a great addition. I don't agree with the idea of adding the statement as the first argument though. It would make the feature unusable for functional programming.

@cullophid
Copy link

I think <| would be a great addition too

@blitmap
Copy link

blitmap commented Apr 30, 2016

Just wanted to add another idea:

What if the pipe operator were a backslash (\) rather than |> ?

Edit: I said the forward slash ^ before (mistake)

I realize |> is used in other languages, but a backslash looks more "pipe-like" in my opinion.

youngest = users
  \ _.select('active')
  \ _.sortBy('age')
  \ _.first
  \ _.get('user')
  \ _.capitalize

Might be worth considering - might not be ~

@dbackeus
Copy link

dbackeus commented May 2, 2016

@blitmap forward slash means division so don't think that's happening.

Actually using pipe | would obviously be the most "pipe-like" thing but I think due to potential confusion with the bitwise operator mimicking Elixir's syntax is a good idea.

@blitmap
Copy link

blitmap commented May 2, 2016

@dbackeus Ahh, I confused myself - I said forward slash but if you look in the examples I used the backslash 😄

Anyway, you're probably right - |> is more well-known from other languages like Elixir.

@vendethiel
Copy link
Collaborator

Backslash is already used by CoffeeScript (line continuations).

@blitmap
Copy link

blitmap commented May 2, 2016

@vendethiel Huh - TIL. I thought then was used for line continuations. So yes, my idea doesn't make sense anymore. Back to |> 😄

@nojvek
Copy link

nojvek commented May 22, 2016

Been six months this is still open. I see there is a es7 proposal for this too. Would be awesome to get this in.

@tad-lispy
Copy link

It's a beautiful idea :)

@thepeoplesbourgeois
Copy link

thepeoplesbourgeois commented Oct 13, 2016

I can do without the entreaties to why syntactic sugars in general are awesome.

My example pointed out the specific utility of syntactic sugars that obviate the need for intermediate variable declaration. Given that your very previous question had to do with intermediate variable declaration, the example felt relevant

@thepeoplesbourgeois
Copy link

thepeoplesbourgeois commented Oct 14, 2016

@GeoffreyBooth I've found some code I believe could be improved by this operator, but before I spend my weekend completely rewriting rewriter.coffee, I should get clarification around a few things:

  1. What are the specific criteria by which you'll judge the success or failure of my refactor? (EDIT) a follow-up: when you asked for it, I gave you a tutorial. Why do you now need this?
  2. What's the threshold for maximum changes I can/should make to demonstrate the utility of this operator? ie., how much code can I change before you tell me to dial it back?
  3. So that I have a reference of how much code you want to see beneficially affected by this change, what was the last feature CoffeeScript implemented that underwent this kind of stress test, and whereabouts in the source code can I find a few samples of its usage?
  4. Are there any potential details of a refactor that would immediately bar my changes from being considered for the code base, and do any recent feature suggestions exemplify such a restriction?

Thank you for your answers.

@GeoffreyBooth
Copy link
Collaborator

@thepeoplesbourgeois to be honest, @jashkenas coming out against something is usually the death knell for a proposal. So I wouldn’t spend much if any more time on this.

Personally I was trying to keep an open mind, but I feel like a lot of the arguments are just rehashes of “look how beautiful it is.” And sure, I get that beautiful syntax can lead to more understandable code, which leads to fewer bugs. So I’m open to being convinced.

But I feel like this becomes less understandable for all but the simplest examples. Sure, this:

result = 'hello'
  |> doubleSay
  |> capitalize
  |> exclaim

is better than the various ways it could be written in CoffeeScript today. But this:

originalObj
  .withSome()
  .chainedMethods()
  |> Array.prototype.yourMethodHere.call(yourMethodArgs)

troubles me, because at a glance it looks like .call takes only one argument, when in fact yourMethodArgs is the second argument, after originalObj.withSome().chainedMethods(). This strikes me as very irregular, and something that I would think that a lot of people would find confusing and/or objectionable.

Obviously another issue is that this currently isn’t compiling correctly, which also means it doesn’t have enough tests. So there’s a lot of work to do on that front, aside from designing the syntax itself such that people aren’t confused by it and that smart people can reliably predict what the output will be. As we saw above with @lydell’s example, it currently also fails that benchmark.

The reason I asked about real-world examples where you’d want to use this, is I wanted to see how often this would likely actually be used. I think the CoffeeScript codebase is a pretty good corpus, because there’s a great variety of code in there and many of the most arcane CoffeeScript features are used in once place or another. You’re welcome to choose something else if there’s another open-source project you know better. And I truly am open to considering examples where the pipe operator would significantly improve the legibility of real-world code as it stands today. But . . . I have a hunch that there wouldn’t be many clear-cut examples you could find where the pipe operator would be a dramatic improvement. How often do people really string together several functions, other than with chaining (which I consider superior to the pipe operator)? And even then, when it does happen I feel like most people do the intermediate-variable approach, like my mutatedObj = example. One could argue that the intermediate-variable pattern is easier to understand than the pipe operator equivalent, even if it’s slightly more verbose.

But this goes back to my earlier question that also went unanswered: isn’t this promoting an anti-pattern? I feel like the current recommended way of dealing with stringing together functions is to take the object-oriented approach, chaining methods: $('button').fadeIn().delay(100).fadeOut(). When is chaining functions, like would be encouraged by a pipe operator, a best practice?

So that’s where I am at the moment. This feels potentially helpful, but also very potentially confusing; it requires a lot more work, which I’m not sure it deserves; and I’m not sure how often it would ever get used, or if we should even be encouraging its use.

@lydell
Copy link
Collaborator

lydell commented Oct 14, 2016

Just a quick note on "bugs" in this PR, or this PR not "compiling correctly" – that's not the case. This PR is not buggy. It's just not doing what people expect in all cases.

(That's actually the hardest part here: Defining what it should do in all cases, and being able to explain that to somebody else.)

@danschumann
Copy link

A business needs to be resistant to change because one customer said so.
This is maybe more of a small crowd, however.

I think it raises a question about order of operations. Everyone knows
multiplication comes before addition for non parenthesized operators.
2_2+2_2 = 8

But we have more operators than that, like 'or'

foo or 2 |> double

Do we get
foo || double(2)
double(foo||2)

How priority is this bad boy, relative to other smooth operators?

On Oct 13, 2016 10:35 PM, "Geoffrey Booth" [email protected] wrote:

@thepeoplesbourgeois https://github.com/thepeoplesbourgeois to be
honest, @jashkenas https://github.com/jashkenas coming out against
something is usually the death knell for a proposal. So I wouldn’t spend
much if any more time on this.

Personally I was trying to keep an open mind, but I feel like a lot of the
arguments are just rehashes of “look how beautiful it is.” And sure, I get
that beautiful syntax can lead to more understandable code, which leads to
fewer bugs. So I’m open to being convinced.

But I feel like this becomes less understandable for all but the
simplest examples. Sure, this:

result = 'hello'
|> doubleSay
|> capitalize
|> exclaim

is better than the various ways it could be written in CoffeeScript today.
But this:

originalObj
.withSome()
.chainedMethods()
|> Array.prototype.yourMethodHere.call(yourMethodArgs)

troubles me, because at a glance it looks like .call takes only one
argument, when in fact yourMethodArgs is the second argument, after
originalObj.withSome().chainedMethods(). This strikes me as very
irregular, and something that I would think that a lot of people would find
confusing and/or objectionable.

Obviously another issue is that this currently isn’t compiling correctly,
which also means it doesn’t have enough tests. So there’s a lot of work to
do on that front, aside from designing the syntax itself such that people
aren’t confused by it and that smart people can reliably predict what the
output will be. As we saw above with @lydell https://github.com/lydell’s
example, it currently also fails that benchmark.

The reason I asked about real-world examples where you’d want to use this,
is I wanted to see how often this would likely actually be used. I think
the CoffeeScript codebase is a pretty good corpus, because there’s a great
variety of code in there and many of the most arcane CoffeeScript features
are used in once place or another. You’re welcome to choose something else
if there’s another open-source project you know better. And I truly am open
to considering examples where the pipe operator would significantly improve
the legibility of real-world code as it stands today. But . . . I have a
hunch that there wouldn’t be many clear-cut examples you could find where
the pipe operator would be a dramatic improvement. How often do people
really string together several functions, other than with chaining (which I
consider superior to the pipe operator)? And even then, when it does happen
I feel like most people do the intermediate-variable approach, like my mutatedObj
= example. One could argue that the intermediate-variable pattern is
easier to understand than the pipe operator equivalent, even if it’s
slightly more verbose.

But this goes back to my earlier question that also went unanswered: isn’t
this promoting an anti-pattern? I feel like the current recommended way of
dealing with stringing together functions is to take the object-oriented
approach, chaining methods: $('button').fadeIn().delay(100).fadeOut().
When is chaining functions, like would be encouraged by a pipe operator, a
best practice?

So that’s where I am at the moment. This feels potentially helpful, but
also very potentially confusing; it requires a lot more work, which I’m not
sure it deserves; and I’m not sure how often it would ever get used, or if
we should even be encouraging its use.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
#4144 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/ABclrXJDCeZVAXFT3O0PN0dSEhc9sSrVks5qzvhxgaJpZM4GhL69
.

@lydell
Copy link
Collaborator

lydell commented Oct 14, 2016

I think it was kind of decided upon that this PR should be changed so that |> gets the lowest priority (precedence) of all (even lower than implicit function invocation!).

1 similar comment
@lydell
Copy link
Collaborator

lydell commented Oct 14, 2016

I think it was kind of decided upon that this PR should be changed so that |> gets the lowest priority (precedence) of all (even lower than implicit function invocation!).

@vendethiel
Copy link
Collaborator

@lydell No, I don't think you want lowest of them all. Think what happens for val = x |> foo in this case.

@lydell
Copy link
Collaborator

lydell commented Oct 14, 2016

Ah, good catch. So kinda lowest or lowest except assignment operators or something then.

@jashkenas
Copy link
Owner

New syntax should have to pass a very high bar to be included, and this is falling quite short.

I think that there's room for discussion about a pipeline-y feature, but it would:

  • Need to be intuitive.
  • Need to be able to support Dart cascade-style repeated calls for side effects (OO style).
  • Need to be able to support repeated operations against the same value. (Functional style).

If it can't do those things, then it probably isn't worth including — and this particular PR+talk doesn't seem like a great base to start from. Closing.

@tad-lispy
Copy link

tad-lispy commented Oct 14, 2016

@jashkenas

I'm happy that you remain open to the pipe idea. Also you are obviously right that design for a new syntax needs to be really sound. Thanks for taking time on this issue.

  • Need to be able to support Dart cascade-style repeated calls for side effects (OO style).
  • Need to be able to support repeated operations against the same value. (Functional style).

I like the idea of cascading. Do you mean that the considered new operator should cover both cascading and piping? The functionality seems pretty different to me - maybe I'm narrow in my thinking.

@jashkenas
Copy link
Owner

I see them as being very related. One is the object-oriented way of applying repeated operations to a value (either for side effects, or to mutate the value itself)

div.show()
  .animate(duration.slow)
  .moveTo(coordinates)
  .blink()

And the other is the functional way to apply repeated operations to a value (either for side effects, or to mutate the value itself)

div |> show
  |> animate(duration.slow)
  |> moveTo(coordinates)
  |> blink

Note that there's strangeness with the piping operator there because it's the first argument to animate and moveTo, but it doesn't appear to be.

If we're going to introduce new syntax, and it can't address both of these at once, the object-oriented style would be preferable to accomplish over the functional one. This is JavaScript, after all.

@tad-lispy
Copy link

Yes, as soon as more than one argument come into play it gets somewhat confusing.

Do you think we can consider any kind of partial application syntax? It seems to me that together they would make more sense.

@tad-lispy
Copy link

Re. relation between cascading and piping.

If we think of this as an additional, implicit argument to the function, then piping and cascading does seem very related. Maybe it should be a hint in eventual future syntax design.

@yetone
Copy link

yetone commented Oct 19, 2016

Just like LiveScript Piping?

http://livescript.net/#operators-piping

@tad-lispy
Copy link

@yetone LiveScript has some awesome qualities and personally I would love to use it, but it's way more difficult than CoffeeScript to introduce in a workplace. It's less known and seems like learning curve for developers accustomed to OO and imperative style is too steep. Same arguments apply here as for the PureScript or Elm mentioned earlier by @igl .

To all those disappointed by the rejection of this PR, here is a somewhat hackish way to avoid inside-out function application without big dependencies. It works by using single-element array as an applicative functor.

Inspiration comes from Professor Frisbee's Mostly Adequate Guide to Functional Programming

# Coerce number to a range and make sure it's a multiple of a different number:

[ result ] = [ number ]
  .map coerce minimum, maximum
  .map round multipleOf
  .map adjust minimum, maximum, multipleOf
  .map log "Coerced number"

# Provided that:

coerce = curry (minimum, maximum, number) ->
  switch
    when number < minimum
      minimum
    when number > maximum
      maximum
    else
      number

round = curry (step, number) ->
  switch
    when not step?
      number
    when number % step is 0
      number
    when (number % step) < (step / 2)
      step * (number // step)
    else
      step * (number // step) + step

adjust = curry (minimum, maximum, step, number) ->
  switch
    when number < minimum
      number + step
    when number > maximum
      number - step
    else
      number

log = curry (label, x) ->
  console.log label, x
  return x

By no means it's perfect as it requires instantiation of an array and only works with curried functions. I hope it's worth sharing anyway. Where there's a will, there's a way :)

@bbugh
Copy link

bbugh commented Jul 26, 2017

Every day I don't have a pipe operator |> (#4144) in any language is a sad day. It's so much more natural than nested calls especially in languages that already chain. I would love if Coffeescript added this one, it fits pretty well I think with how Coffeescript works already. Hopefully Coffeescript 2 will consider this!

@thepeoplesbourgeois
Copy link

thepeoplesbourgeois commented Aug 11, 2017

I will say that it's odd but somewhat comforting that the CoffeeScript committee is showing restraint compared to TC39 around questions like this one. Having no answer to a problem is better than having a very wrong answer...

Though, the decisions they're making while mad with power, and the blazing agility with which the Babel transpiler can adapt to those whims via its plugin architecture leads me to beg the question: Could I not simply plugin-architecture this functionality into my own deploy of the CS transpiler, if it were so vital to my workflow?

@aurium
Copy link
Contributor

aurium commented Jan 25, 2018

@jessaustin
Copy link
Contributor

jessaustin commented Jan 25, 2018

@aurium the example at that link is amusing because f∘ g∘ f is always commutative. It seems deliberately confusing about the fact that x |> h |> g |> f is equivalent to f(g(h(x))).

@danschumann
Copy link

The pipe is the language equivalent of "go into"
x goes into h
x |> h
h(x)

x goes into h, which go into g
(x |> h) |> g
(or apparently by the order of operations is not needed to have parenthesis)
g(h(x))

I would probably not recommend using too many of these.. but I can see how it would be nice to have.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.