-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Proposal: Deprecate then remove function piping #20331
Comments
Function piping provides a postfix syntax for function calling, which is convenient at the REPL for interactive data generation and further visualization/summarization. A use case that I have seen many people type is julia> somecomplicatedthingproducingarray
...
<ARROW UP>
julia> somecomplicatedthingproducingarray |> summarize where the |
@jiahao I'm not arguing that it's not useful, but rather that we should be consistent within Base and let packages provide things like this. |
there's also |
In this proposal would |
@ajkeller34: definitely, packages would be free to do whatever they want with it (though they'd have to play nicely with each other in terms of type piracy and coexistence), without as much of a constraint of being semantically compatible with the old base definition.
Here's a now-very-outdated attempt I made to do this: tkelman@212727c |
In case anyone is curious, I have ChainRecursive.jl out now. I'll put an announcement on discourse about the disintegration of ChainMap.jl and its various children once it's complete. |
Let me offer some resistance here since I have some vested interest and a particular liking to what I second with @jiahao that I know that after this I can define
The alternative to |
Escher's use of closures as objects seems to me like it's defining a DSL just for the sake of using this syntax (which has serious limitations for anything that isn't single-input, single-output), where it would likely be better-served, and more generalizable, if it used one of the multiple available chaining macros. Removing Base's definition of this would allow people who like this syntax to do more interesting things with it. |
@shashi I understand your points, but you would be able to get the same behavior using one of the packages I cited in the issue, would you not? As an example, in your Escher example, you could use FunctionalData to do |
Except it will not be usable, since the only safe way to use it then would be |
Hypothetically, how would one use |
@kmsquire It depends on the use case. Take for example julia> dump(:(a <| b))
Expr
head: Symbol call
args: Array{Any}((3,))
1: Symbol <|
2: Symbol a
3: Symbol b
typ: Any Packages can define and export methods for But the packages that provide nice function piping do so in macros, I assume for precisely this reason. |
FWIW, no chaining package would need to make use of |
If we're deprecating this should we also deprecate More generally, we should probably deprecate all infix notation, as it's confusing to have multiple calling conventions like |
Still not sure why we need to remove it from Base. @bramtayl makes a good point:
And still the only way to use more than one package which define this is to not use it infix. |
It isn't. My point is that |
@ararslan right, that was not what I meant to ask, I updated my comment right after, sorry. Anyway, I don't quite get the "Base self-contained in terms of function calls" sentiment. Seems like this will only make it harder to use |
Maybe just something formal: Please discuss/decide deprecations and/or syntax changes at the beginning of a release cycle, not at the end. Currently all the main developers and package responsible spend time and energy on finishing 0.6 and they just might have no time to think of another (good) idea. |
Sometimes usefulness beats consistency? I wasn't aware of the inconsistency, but I have found the |
An explanation for my thumbs-down vote, if I may: Much of what's currently in Base could happen in packages instead. Should we move dictionaries to a package? Maybe list operations like sort and shuffle? Collections operations, etc.? I'm sure there have been long and detailed discussions concerning what should and shouldn't be included in Base, but I presume there are three reasons some functionality might be included in base:
Something like In both the initial (well-written) proposal and the discussion in this thread, a common theme is the existence of several packages providing pipe-like functionality through macros: Lazy.jl, Pipe.jl, ChainMap.jl, etc. The existence of multiple packages strongly suggests that many people in the community find piping a useful and desirable feature, and these packages' presence in this discussion thread suggests that many folks here understand and support the use of piping. Given that piping is a common and popular feature in the Julia community and other languages, even in this discussion people seem to agree that it has many uses, especially at the REPL (where Julia shines), and there's already fragmentation in the Julia ecosystem...my read is not that it should be removed from Base, but rather that the piping syntax available in Base should be enhanced so that there's less need for fragmentation. Different packages offering different ways of e.g. plotting seems okay; different popular packages offering different ways of applying functions seems pretty scary. I further argue that removing piping from Base but leaving the infix operator around is rather surprising: in Julia you can't define your own infix operators, but there is an unused infix operator Lastly, I believe it's natural to keep piping exactly because it is different from other function application. It's a feature, not a bug, that it's different from other conventions of applying functions; this difference is what lets it shine in some use cases. And there are other cases where (hand-waving a bit) the noun comes before the verb, and many of these are exactly syntactic sugar in cases where raw function application is unwieldy. Off the top of my head, assignment |
There's probably more than that if one includes all unicode ones in addition to the unclaimed ASCII ones like |
Not reading whole thread -- but just wanted to say |
I have a very mild preference to keep it, but don't really care so long as it remains an infix operator. I feel like I probably wouldn't use function piping if it entailed importing a package, which tells me that I don't value it very much. That being said, I don't think this "principle of least surprise" argument is compelling, as it makes some presumptions about a diverse user base. To native speakers of subject-object-verb languages, I suppose most of Julia's syntax violates the principle of least surprise, and function piping is rather comfortable... |
😕
Again, I'm not arguing that one should not be able to pipe, but rather that the functionality could easily be had instead in one of the several existing piping packages. Removing the Base pipe allows for packages to more easily define their own piping semantics without having to adhere to or remain consistent with whatever Base provides.
That's not true; anything that parses as an infix operator can be defined or redefined. As martinholters pointed out, |
I'm kind of neutral on this one, but I will second the sentiment that |
I'd like for |
@StefanKarpinski More powerful pipes can already be obtained using macros. See for example Pipe.jl, which provides exactly the syntax you're describing. As long as |
We could add the functionality of Pipe.jl to the language, and then you'd have it without needing to write The main reason to deprecate |
I guess I'm trying to argue that piping doesn't need to be part of the language, it can (and already does) live in a package. |
But if there's nothing else we want |
I don't believe there are currently any proposals to repurpose |
Would any "more powerful piping" proposals or package implementations be made simpler by not having this existing definition to worry about or work around? |
@ararslan "That's not true; anything that parses as an infix operator can be defined or redefined." From the manual "&& and || operators", they are parsed but can't be redefined (it's a good thing). I believe the only exceptions. The so-called "logical operators" && and || are infix. [unary binary relation] "operator" is IMHO the incorrect term for them as they aren't. Not is a similar way to the logical bitwise & and | that do allow overloading (something I'm not sure is a good choice). |
@PallHaraldsson Those are control flow, not operators in the same sense as Let's try to stay on topic here if possible, please. |
@tkelman That's a good point. I suspect we can make future piping syntax backwards-compatible though. For example, if There's another issue: to make |
The consensus here is very clearly against, so I'll go ahead and close the issue. I appreciate the discussion, everyone. |
I know this issue is closed. Just wanted to say "thank you" for keeping the operator. |
Proposal
Deprecate the current use of
|>
as a function pipe. That is, the syntaxx |> f
would be deprecated in favor of the normal call syntaxf(x)
. After the deprecation period,Base.:(|>)
would be undefined.This change was initially suggested by tkelman in #16985 (comment).
There has been a lot of contentious debate over various syntaxes for function piping (in particular, see #5571), with arguments for mimicking a variety of languages. That discussion has been had ad nauseum and I do not wish to rehash it. That is NOT the purpose of this proposal.
Rationale
A number of well thought out, well maintained packages have implemented macros that provide convenient piping syntax for a variety of use cases, both general and specific. Examples include Lazy.jl, FunctionalData.jl, Pipe.jl, and ChainMap.jl, among others.
StefanKarpinski and andyferris gave us arbitrary function composition in #17155, which can serve a similar purpose in many situations.
As tkelman similarly argued in #5571, the function pipeline in Base is backwards from the familiar call syntax; having both in the Base language is essentially endorsing the use of 2 disparate syntaxes to achieve the same goal. While there are often multiple ways to write the same thing using solutions in Base, typically the solutions at least adhere to a similar mental model. In this case, the syntaxes employ literally opposite mental models.
Function pipelines violate the principle of least surprise by applying the action after the object. That is, if you read
sum(x)
you know immediately when you seesum()
that you're going to add up the values in the argument. When you seex |> sum
, you seex
, then all of a sudden you're adding up its values. Few if any other Base solutions put the action at the end, which makes piping the odd one out.Piping does indeed have precedent in other languages, e.g. Hadley Wickham's
%>%
in R (which is not part of base R), and sometimes that style/flow makes sense. However, in the interest of consistency within Base Julia, I propose that we defer the responsibility for providing piping syntax to packages, which can redefine|>
or provide convenience macros as they see fit.Action Items
Should this proposal be accepted, the action items would be:
Base.:(|>)
in either 0.6 or 1.0The text was updated successfully, but these errors were encountered: