-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Include pipe operator '|>' in ChainingOps
#7326
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
Conversation
|
Why do we want to have |
|
I'm 👍, but I wouldn't be against keeping |
| * `f` to this value. | ||
| */ | ||
| def pipe[B](f: A => B): B = f(self) | ||
| def |>[B](f: A => B): B = f(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO if |> will be introduced it has to be an alias.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Elixir has the |>:) too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, just add an alias.
| .pipe(times6) | ||
| .pipe(scala.math.abs) | ||
| val plus3 = (_: Int) + 3 | ||
| val result = (1 - 2) |> plus3 |> scala.math.abs |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the user doesn't like postfix syntax ?
You may want to add an import: import scala.languageFeature.postfixOps
I think pipe() should be there for us who do not like ascii art in our code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will it look on multiple lines?
val result = (1 - 2 - 3)
|> times6
|> scala.math.absDoes that parse ok? Or would it need surrounding parentheses?
If not, I'm not sure these:
val result = ((1 - 2 - 3)
|> times6
|> scala.math.abs)
val result = (1 - 2 - 3)
.|>(times6)
.|>(scala.math.abs)are nicer than with .pipe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if the user doesn't like postfix syntax ?
@martin-g eh? there isn't any postfix syntax here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm guessing you meant infix, rather than postfix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately it does not parse, but like in the other languages, I think it should. But that shall be handled as a separate matter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd just write
val result = (
(1 - 2 - 3)
|> times6
|> scala.math.abs
)but I know there are people who think that looks ugly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would too, if it compiled :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Welcome to Scala 2.13.0-20181012-195043-19fa66d (Java HotSpot(TM) 64-Bit Server VM, Java 1.8.0_161).
Type in expressions for evaluation. Or try :help.
scala> import scala.util.chaining._
import scala.util.chaining._
scala> def times6(i: Int) = i*6
times6: (i: Int)Int
scala> val result = (
| (1 - 2 - 3)
| |> times6
| |> scala.math.abs
| )
result: Int = 24it does! :-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, did the brackets do the trick? Excellent. Will change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, can't infer a semi-colon within parentheses.
|
I suggest marking this (and pipe) as @inline |
OCaml
F#Function Composition and Pipelining:
Elixir
ECMAScript proposalESNext Proposal: The Pipeline Operator Swift libraryR library
|
|
Here's my original reason for calling it |
|
I'm on the fence about this. The thing is, any one symbolic operator considered in isolation looks appealing. The But the problem is symbol overload in the language, standard library, and ecosystem as a whole. |
|
I'd be happy for both I agree that any new operators should be challenged and this is good to see. I really dislike However, I'll mention ReasonML again, the multi-lining comment in the OP is exactly how I use pipe.
|
|
Sometimes symbolic operators like that look nice on paper but quickly fall flat on their face. Consider a case where we reach a type inference limitation and have to supply the type parameter explicitly: error |> [Either[String, Int](Left)yuck What about formatting a long chain of pipe calls across many lines: foo
.|>(bar)
.|>(baz)
...
.getTheGoodStuffnot cool What if we want to use case function syntax (and we also have to supply the type parameter and we also want to format): foo
.|>[Either[String, Int]] {
case Bar => Left("error")
case Baz => Right(42)
}.|>(...)This reads like a soup of symbols to me. |
If you want to use dots/braces use This would be written as something like: |
|
Btw, whatever the end result, thank you @ScalaWilliam for leading the effort here. |
|
+1 for having |
|
I think we should keep |
|
+1 for having |> as an alias to pipe |
|
I'll make it an alias. |
|> in ChainingOpsChainingOps
|
Applied the change. |
|
@ScalaWilliam I don't think you can remove the Scaladoc comment with just |
|
Right. Thanks for clarifying. |
|
I'm trying these out on a real codebase... and noticed just how similar |
|
Yes, hence my comment somewhere or other that we should just allow |
|
@Ichoran that sounds interesting. |
|
For multiline: I wish we could do this (& imagine the beauty when the pipes are all aligned!): 1 |> plusOne
|> plusOne
|> plusTwo
|> plusThreebut sadly have to do this instead: 1 |>
plusOne |>
plusOne |>
plusTwo |>
plusThree |
|
For single line: val two1 = one |> plusOne |> plusTwo |> plusThree
val two2 = one.pipe(plusOne).pipe(plusTwo).pipe(plusThree)
val two3 = one pipe plusOne pipe plusTwo pipe plusThreeThe first is definitely the most obvious and easiest for scanning, the third is the worst. val two2 = one
.pipe(plusOne)
.pipe(plusTwo)
.pipe(plusThree) |
|
Ref Pre SIP: Demote match keyword to a method. Another what-if possibility, that might be considered Scala idiomatic is treating val two2 = Id(one)
.map(plusOne)
.map(plusTwo)
.map(plusThree) |
|
@eed3si9n that is very interesting. Pity that I missed all these discussions. |
Many years ago I proposed that all Functions had A consequence of this would be that there'd be no more PartialFunction type, and there would have to be no language support for |
|
@ScalaWilliam That's a shame about the multiline... but the example shows a solid use case for having |
@ScalaWilliam actually you can use List(Some(1), None) map {
case Some(_) => true
case None => false
}The resulting function will throw a MatchError if a value isn't handled, just like |
The operator is common throughout many programming languages and is a familiar syntax to many, especially those coming from Bash and F#. Work done: - Added the `|>` method to `ChainingOps`. - Simplified the examples for `|>` so they are less ambiguous. In particular, as `* 6` and `math.abs` are commutative, we must demonstrate a clear order of chaining application with a non-commutative example. - Added `@inline` to minimise performance impact.
|
Please see the updated commit. I returned the comment regarding overhead, but put it at the comment of the |
| /** Adds chaining methods `tap` and `pipe` to every type. | ||
| /** Adds chaining methods `tap`, `pipe` (aliased as `|>`) to every type. | ||
| * | ||
| * Note: these methods may have a small amount of overhead compared their expanded forms. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would add "or the equivalent match statement".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's also a missing preposition after "compared"
|
If I understand correctly, naming it Also it promotes the Id monad, which may make more people learn what it is and dive into FP |
|
Seems it's converged now. |
True. But it also would mean that any value could be used in a
True, but the "ambient" |
|
to summarize the above discussion, to help final review:
personally, I half want to support this but I'm still wringing my mental hands about "symbol overload in the language, standard library, and ecosystem as a whole", as I said above I'll try to find out what the rest of the team here at Lightbend thinks |
|
The |
|
I don't mind removing them. |
English method name is our defaultUltimately, it comes down to subjective taste of how code written in idiomatic Scala should feel like. The reason why I am drawing comparison with isometric val something = List("foo")
.map({ case "foo" => 2; case _ => 3 })
.map(plusTwo)
.map(plusThree)The above hopefully feels idiomatic to many readers, and it does not use symbolic operator to process passed in functions. We say when symbolic DSLs are used todayThere are certain exceptions to the general English rule, and I can think of the following motivations:
Some examples of the first might be The latter example might be Note that some of these are actively being deprecated in Scala 2.13. (could someone link to why what this means for
|
Remove `@inline`
Grammar fix
|
I'm going to close this, on overall-language-surface-area-especially-when-symbolic grounds, unless someone else from the Scala team speaks up clearly in favor of merging it. going once, going twice, ... |
|
To further explain my 👎: I don't feel that strongly that we need this, but:
So I think on balance it should be merged. Ideally, we would be able to merge this in some kind of non-committal way, but I don't think we really have one... We're moving away from compiler flags, experimenting in Dotty puts it too far out of reach, having it in an external library also puts it out of reach. The other options I see are annotating it with Or we can just accept this one-liner, and go through the regular deprecation cycle if we change our minds later. 🙂 |
|
Proposing |
|
@SethTisue - I'm throwing this one back to you. I don't know how to interpret the discussion, and I object to having |
|
So sad |
This is in follow up to the excellent PR by @eed3si9n at #7007.
The operator is common throughout many programming languages and is a familiar syntax to many, especially those coming from Bash and F#.
I modified the test examples, so that the order of function application is completely unambiguous. *6 and math.abs in this particular case make it which way round the functions are applied, so I replaced it with a simplified test case that makes it clearer.
This is the one symbol that is good to have as a symbol. It is very special.
Inspired by @eed3si9n's comment here: https://github.com/scala/scala/pull/7007/files#r222332538
Multi-line:
What I especially enjoyed in F# days, and also love with Bash, is being able to multi-line it:
This would sadly yield a compile error but I'm sure such syntax could be added eventually.