The parens are not mandatory, but they’re typically encouraged in Elixir. After all, the whole thing is just sugar for String.reverse(String.upcase(my_string))
It's a different perspective, the composition operator (g . f) operates on two functions whereas pipe operators operate on a value and a function (f x |> g).
I too like the former, it's more flexible as you can easily put the values through after you composed the functions.
It's nice that the operator is left associative but it still doesn't let you combine functions into bigger functions.
Composition is really helpful when working with higher order functions. E.g. map (toString . double) [1,2,3] (evaluating to ["2",",4","6"]) which I think reads really nicely.
A left-associative reverse-composition operator (.>) would be applicable here, e.g. [1,2,3] |> map (double .> toString) flows nicely left-to-right. Too bad Haskell didn't include something like that in the Prelude along with ..
It's a common complaint that people think that the composition operator works in the wrong order. To me it makes sense the way it is when you think of where it's coming from.
y = g (f x)
y = g $ f x
y = (g . f) x
Data flows from right to left when we assign values with = and especially when we put it through a function first.
Maybe I didn't write enough Haskell, but I always had to translate function composition "manually" in my head. The pipeline operator just fits the reading direction so much better, and requires less of a "stack" memory in your head. Kind of like how in german, you have to reach the end of the composition before you can understand what happens.
At least in principle. They would be better with reverse noun-verb order (you have way more options starting with "Get-" than starting with "File-"), plus all the exceptions and bit unclear closures/flags etc.
If you know the noun there is nothing stopping you from writing: *-Noun<Ctrl+Space> to list out all the verbs for that noun. Anyway, they have talked about this and essentially the reason boils down to Verb-Noun being easier to read and understand, especially for sysadmins.
I also think it plays nicely with how module authors typically name modules to ensure they are unique. In many cases they add a known prefix for every command in the module, for example the ActiveDirectory module starts every noun with "AD" like: Get-ADUser, Get-ADComputer, Get-ADGroup and so on. This means you can type in Get-AD<Ctrl+Space> and get a full list of everything you can Get. With the Noun-Verb pattern it would only show commands that match your noun exactly.
If you know Haskell, "modern" syntax features like this look laughable in comparison. It's cool, don't get me wrong, but Haskell does these things properly.
Hard disagree. Haskell uses so many custom operators that it becomes indecipherable line noise. There's nothing wrong with the pipe operator itself, but there is something wrong when you have 30 different varieties of composition operators with God knows what order of precedence.
Haskell does both function composition and member access with ., which is interesting to me. In my never happening language -> would be reserved for member access and . for composition. I think I'm the only one who'd want it like that
The pipeline operator |> here is actually the reverse application operator & in Haskell, distinct from the function composition operator .. I prefer writing uniform left-to-right functional pipelines, so in my language Admiran I have reverse application (|>), reverse composition (.>) and monadic bind (>>=) which can be intermixed in a uniform left-to-right pipeline.
I haven't actually written any Haskell (which is criminial considering I'm endorsing functional programming, I know), so I'll have to check it out.
I've really been meaning to give Haskell a shot, but as I'm more of a newbie to FP I've been focusing largely on OCaml thus far (and also enjoy Elixir as you could probably tell from the article haha).
I think the pipe operator in general is nice for me because it helps me model the idea of "input -> data transformation -> output" if that makes sense.
Yeah I think the pipeline operator makes sense too, but somehow I prefer function composition. I'm no hardcore functional programmer so I'm not sure what I'd think if I did more of it
PHP just got the pipe operator in 8.5 and I haven’t had a chance to use it yet, but we use method chaining all the time, so I’m excited to have the option to use a similar setup with functions. Larry Garfield has been really pushing the FP functionality in PHP a lot and while I don’t understand it yet I’m glad to have the paradigm available as technology keeps moving forward.
This is the curse of languages, that have been badly designed initially. Most of them get stuck with backwards compatibility and having to maintain their old stuff, that doesn't work well with the new stuff and it's overall holding the language back.
PHP now trying to get more functional is laudable. And a pipe operator or way of threading function calls is great to have. Though one thing needs to be kept in mind: It is most useful, when one is threading calls to pure functions, not mere procedures with side effects. PHP is not a languages, that encourages functional style. Its standard library is all about mutating stuff, at least last time I checked, and I have not read or heard much about functional data structures in PHP either.
As usual when such a thing is bolted on instead of the language design adapted to include it and encourage it from first principles, it will work, but probably not be as great as in languages, that were built to support the new thing from the get go.
$result = "Hello World" |> strtoupper |> str_shuffle |> trim;
Warning: Uncaught Error: Undefined constant "strtoupper" in php shell code:1
Stack trace:
#0 {main}
thrown in php shell code on line 1
Ah too bad. A language that considers functions as first class citizen would probably have supported that. But unfortunately, lots of syntactic clutter is needed. It is working, but it won't win a beauty price. It's PHP.
First it was OOP that was bolted on top, now it is a few things more common in FP first languages. Improves things a little bit, but in either direction doesn't get close to languages doing things from first principles. PHP will likely remain a hodgepodge language.
Yup. Its just a big ball of mud. No design went into this feature, its yet again a bolt-on, like most of php "new" features. They just copy from (rolls dice) and adds some feature (that usually brings little value) that works with like 40% of the already included stuff.
this works on the other hand, even though it looks weird, and nobody would use it. Just the old "I need a callable" syntax before (...) and closures where introduced
Which part of the standard libraries does it work poorly with? Again, I haven’t gotten to use it myself, so I’d love some first hand experience about the footguns
Function *chaining* is not a true analog to the pipe operator. Chaining relies on state in a common `this` the type of which must be returned by each function. As a consequence, call chaining in OOP is constrained by static design:
this.foo().bar().baz() // limited to calls on `this`
The true analog is function call *nesting* where as with piping the output of one function is the input to the other:
baz(bar(foo()))
This inside-out syntax is harder to read than the more natural flow with pipes:
This is all true. What I intended to communicate was the pattern goes from inside out to chaining left to right style, as you pointed out. Right now my biggest bugbear with them (and I don’t know how serious of an issue it is) is they don’t seem to have adopted the null safe operator for the pipe system, so object methods can be run like
And if any of the methods would return NULL the chain terminates and $result is null without errors. But it doesn’t look like the pipe operator has that same option
Fwiw, functional langs tend to place the onus on the callee to explicitly handle null. Meh. Not the best design.
In my view with PHP we are better off using chaining and nesting. The pipe offers left-to-right flow, but honestly the readability difference is subjective here; a bone thrown to the functional-minded.
It’s also prep for making the functional paradigm more possible in PHP. You can read more about why it’s important and what comes next here https://thephp.foundation/blog/2025/07/11/php-85-adds-pipe-operator/. The Partial Function Application is already approved for 8.6, so we’re now at 2/3rds of what Larry thinks we need for more robust functional features.
Also that blog gives an example of how to handle nullables. So that’s nice. I just need to remember it.
PHP gets lots of shit but it’s been steadily improving for years, in both the language and the ecosystem, and thanks to Wordpress (not my favorite) it still runs a good chunk of the internet. Totally worth knowing if you’re a web dev.
I don’t need concurrency in my day to day work. Or at least not beyond the FPM child process system to run multiple threads at once. So I’ve never really worked on it. I’m still wrapping my brain around fibers and stack handling for stuff like that. But also 8.6 is going to include partial function application, which will allow for some shenanigans when it comes to calling functions without having all the required pieces yet. So that might help (my concurrency chops are woefully limited).
Unicode handling might not be in PHP core, but the mb and intl libs make handling it pretty simple, and those libs are maintained by the PHP foundation, not 3rd parties, so they might as well be core, you just have to turn them on.
This depends heavily on your level of experience and career goals.
If you're a junior and struggling to break into the market, then hammering away on some WordPress plugins or themes is fine: it's a necessary evil and a wedge into something real. Nobody can rightly blame you. Do it while you still have the window available, AI is already replacing you.
The company whose bread and butter is PHP in 2026 is either an abject failure, in a specific porn niche, only partially maintaining it while moving to anything else with longevity, or otherwise somehow ineptly yet to realize their obvious death spiral.
I'm not trying to be cruel with this: if you're in some weird Stockholm Syndrome rambling about Symfony or whatever, you need to spend the time and move on or your career is toast.
That's a bit of a weird opinion to have, at least if you don't support it with some numbers. Php is getting used less and less but it's still one of the largest languages out there.
Besides, Symfony's very similar to Java's Spring Boot, so it's not like you're stuck with php forever if you learn how Symfony works.
It feels like so long ago I was deep into PHP. Is it mostly legacy code on it today? I think it would be fun to port some to something modern, but if it ain't broke...
Laravel and Symfony are 2 of the biggest frameworks. I personally havent used laravel, but symfony is great and what I been using for the past decade professionally.
There is a ton of legacy code lying around, for sure. But there are also lots of modern frameworks that update frequently and are in no way legacy (Laravel is the one I am most familiar with). The CMS Drupal (my primary day job) is updating new major versions every 2 years, with upgrade paths built in. You’ll always find modules or plugins that are lagging and legacy, but honestly if they’re using any modern techniques like composer and PSRs then patching isn’t usually difficult.
Yeah that's a fair statement, and to be honest sometimes keeping up with the JS ecosystem is frankly exhausting.
It's part of the reason I've actually been going more into the CS stuff and away from web dev, but maybe PHP will bring back some of that energy. The web is an awesome platform.
pipes in linux are so much more than method chaining. Method chaining is a series of synchronous operations, one after the other, with the output from one being supplied as input to the next at completion. A pipe is a mono or even bidirectional communication between two concurrent processes in linux, each with their own PID and environment.
The real fun begins when someone stops to consider what happens when one of the steps in the pipeline can return a nullish or error result, but you don't want to perform a guard check on every step. To paraphrase Emperor Palpatine, function composition is a pathway to many abilities some consider to be unnatural.
That's what I mean. At at a certain point people are going to want to put their intermediate values in a box and control the composition logic, and then before you know it you're in a world of monoids and endofunctors.
Adding to the above, if any readers want to learn about what these two are discussion there's a great article from an F# point of view on "railway oriented programming"
void LocalError() => ...;
var x = xfunc().OnNullishOrError(LocalError).yfunc();
I don't see how's that different from the non-chained version. Sure you need machinery around all that, but this kind of encourages reusability (or rather plugability) instead of case-by-case error resolution handling
I don’t like it. What this (and many functional features) does is give programmers an opportunity to do something without picking a name for the intermediate values. Those names are quite valuable when trying to read code later.
Programmers already have the 'opportunity' to not pick names for intermediate values: h(g(f(x))). That's just normal function call syntax. You have that whether you're using a functional programming language or not. The pipe operator at least lets you visualize the data flow: x |> f |> g |> h.
As always, it's up to the programmer's good judgment whether intermediate named variables are needed or not. No language or paradigm can replace that.
Nothing is stopping people from nesting function calls like a motherfucker with or without the pipeline operator:
function whateverLol(a, b, c) {
return validate(lol(data(value(a,b),c))));
}
People who are dogshit at naming things will find a way regardless of what operators they have at their disposal. So long as they can name an identifier, they’ll find a way to make it make as little sense as possible.
Genuine question: why would you need names for the intermediate values?
If your goal is to transform input into a certain output, and the path to which that achieved is clear, why not use the pipe operator?
Or are you suggesting that both the method chaining example in the post and the pipe example are both wrong, and instead it's more ideal to just split everything up into separate variables?
I've found that function naming is generally sufficient for describing what is going on. With pipelining its easy to describe each part of the process as an individual function. Which gives you the secondary bonus of making each function smaller and easier to test.
Or are you suggesting that both the method chaining example in the post and the pipe example are both wrong, and instead it's more ideal to just split everything up into separate variables?
There's a trade-off; GP perhaps would like more clarity about what the intermediate steps mean when reading code, but your example is basically the best-case scenario for chaining (whether you are chaining via an operator or method calls is irrelevant to him, I would think).
This makes comprehension difficult, debugging almost impossible and logging actually impossible.
What if myData was data from outside the program (fetch call, user input, etc) and we got the wrong data? All we see is an exception.
What shape does constrain() result in? Is it a table? Is it a tree? Something else?
What does normalise() result in? Is it fixing up known data errors? Is it checking references are valid?
All we really know is what deduplicate() returns.
We cannot log the time between each step, even temporarily. We cannot log the result of each step. We can't introduce unwinding steps if this is stateful.
This differs a lot from the best-case scenario you present, and to be fair, your example is the most common type of usage for this sort of thing, and I wouldn't hesitate to use it in production. What I would not do is choose a chained approach for functions/methods that are not standard.
We cannot log the time between each step, even temporarily. We cannot log the result of each step. We can't introduce unwinding steps if this is stateful.
Depends on the language for sure, but if you're doing a lot of chaining like this, and don't have access to the |> operator to slot in arbitrary generic functions, putting this on your class is great.
This makes comprehension difficult, debugging almost impossible and logging actually impossible.
What if myData was data from outside the program (fetch call, user input, etc) and we got the wrong data? All we see is an exception.
Have the functions log the errors, or maybe even have them return a Result type, monad style.
What shape does constrain() result in? Is it a table? Is it a tree? Something else?
What does normalise() result in? Is it fixing up known data errors? Is it checking references are valid?
Obviously this is a general example, but these methods take a one/two thing in, and spit one thing out, so I'm not sure how you're supposed to clarify those intermediary steps with more info, outside of literally saying what the method is doing in particular. With context, if this was particular data for a particular purpose, sure, add an intermediary name if you want, but if we're just dealing with generic "data" or that context is already provided elsewhere (e.g. the surrounding function) you would just have code like this:
We cannot log the time between each step, even temporarily. We cannot log the result of each step. We can't introduce unwinding steps if this is stateful.
If you need to log the result of each step or unwind, then split it up and do that, but if you don't need to do that, then just chain them. Its fine.
Pls show some respect to Hungarian Notation. crszkvc30tempVarIntermediateValue. Anyone who can't tell from the name shouldn't be programming anything bar laundry cycles.
Seriously, Hungarian Notation just makes everything so much clearer. For instance, your variable crszkvc30tempVarIntermediateValue. This is clearly a temporary variable whose intent is to be used as an intermediary value between operations on a temporary basis, whose length is fixed to 30 characters and whose valid range of values are exclusively limited to words in Polish.
What I often see, is that it isn't entirely obvious what the individual piping steps do. That is because a function is used in a way that doesn't explicitly match its function name. Also, I see large number of arguments for individual steps that make it difficult to follow the piping (which can be addressed with whitespace usage).
The result is that piping is only clear when things are sufficiently simple (and always looks good in sample code). But, my experience, is things get more complicated over time, such as arguments added to functions. So, piping will eventually become unclear, at least in a large long-living project.
I have the same reservations about chaining.
I will say that there are some cases where the function of the intermediate values is very obvious and piping does remove some unnecessary verbosity.
It's one of those things that has to be used in moderation. Completely unchained code can be harder to read because of all the useless intermediate variables. But excessive chaining can be hard to read because it's easy to lose teach of what's going on. It can be good to break up the chain at major milestones to provide a sort of mental checkpoint.
I do so where you're coming from, yeah I imagine it's something where it's like "it depends". Some other commenters also gave some good pushback on when you should/shouldn't use it.
Appreciate the response regardless, hope my initial comment didn't come off too abrasive :)
For one it is documentation for people trying to understand your code later. But the big thing is that when something in that big pipe isn't working it can be very difficult to track down where the error is happening when everything is anonymous. Having the intermediate values split out also allows you to inspect the contents of those temporary variables to see where something has gone wrong.
I’m actually of the complete opposite opinion. The great value is precisely that you’re not forced to come up with bogus intermediate names. Famously, “There are only two hard things in Computer Science: cache invalidation and naming things.”.
And language servers can provide inline hints for what the types are. That doesn't help reviewers who rely on just reading the diff, unless we get language server-powered diffing tools.
The author uses Elixir as an example of a functional language with pipes, and it seems interesting, but R is a more notable example (#9 on the Tiobe index vs. Elixir's #42).
Here is the R code for the proposed operation (string uppercase, split, reverse, flatten).
library(tidyverse)
"A wizard is never late." |>
str_to_upper() |>
str_split_1("") |>
rev() |>
str_flatten()
#> [1] ".ETAL REVEN SI DRAZIW A"
This is so funny cause it looks so much worse to me, and also devex is worse as well.
Now, i may not be a big city programmer, but when I call "test".ToUpper(), My intellisense will autocomplete the method call as I'm typing it, and also give me the entire list of possible methods to call on this instance of a string. It also gives me the return type, so I know if the method modifies the string or returns a new one.
The pipe operator breaks the typing, i assume it also doesn't understand the context of its call as much. You have to type out the 'String.' manually before you get only string specific methods. C# also includes extension methods and inherited methods, that wouldn't show up on static 'String.' calls.
That's false. If you have an expression like f(a) | g | h
then by the time you type out the pipe operator, you know the return type of the previous part and can offer only relevant functions that take that type as a parameter.
Actually in F# using piping often gives it enough context to infer the type, which means you can just drop the type from the function signature. This gives it a less noisy, mathematical look.
I'd be interested to see this sort of thing in Ruby. We already have the rocket operator => to spaff out the contents of a hash into a variable, so I think the same operator could be used for this as they're semantically similar imo
the pipeline operator is one of those features that makes you realize how much mental overhead nested function calls actually have. reading result = h(g(f(x))) vs x |> f |> g |> h is like reading a sentence backwards vs forwards.
used it heavily in elixir and going back to JS where i have to chain .then() or nest calls felt painful. the TC39 proposal has been stuck for years tho and at this point im not sure itll ever land in vanilla JS. typescript could adopt it independently but they historically avoid syntax that doesnt exist in JS.
for now i just use small pipeline helper functions. not as pretty but gets the job done without waiting for committee consensus
what kind of example do you need? cascade becomes the elegant solution for repeated message sends and you don't care much if they have multiple parameters or not
QuineQuest@reddit
I don't know Elixir, so I have a question: In the second code block, shouldn't it look like this:
Without the
()afterupcaseandreverse?Also, the last example with JS could be better. It does the same as the first code block, but is longer and less readable.
Sentreen@reddit
The parens are not mandatory, but they’re typically encouraged in Elixir. After all, the whole thing is just sugar for
String.reverse(String.upcase(my_string))Jhuyt@reddit
I know I'm in the minority, but I aesthetically prefer the haskell way, which uses the function composition operator.
AxelLuktarGott@reddit
It's a different perspective, the composition operator (
g . f) operates on two functions whereas pipe operators operate on a value and a function (f x |> g).I too like the former, it's more flexible as you can easily put the values through after you composed the functions.
phillipcarter2@reddit
you'd write this as
x |> f |> gif it were in F# or OCAML fwiwAxelLuktarGott@reddit
It's nice that the operator is left associative but it still doesn't let you combine functions into bigger functions.
Composition is really helpful when working with higher order functions. E.g.
map (toString . double) [1,2,3](evaluating to["2",",4","6"]) which I think reads really nicely.phillipcarter2@reddit
It’s what the >> and << operators are for.
AustinVelonaut@reddit
A left-associative reverse-composition operator (
.>) would be applicable here, e.g.[1,2,3] |> map (double .> toString)flows nicely left-to-right. Too bad Haskell didn't include something like that in the Prelude along with..AxelLuktarGott@reddit
It's a common complaint that people think that the composition operator works in the wrong order. To me it makes sense the way it is when you think of where it's coming from.
Data flows from right to left when we assign values with
=and especially when we put it through a function first.Own-Zebra-2663@reddit
Maybe I didn't write enough Haskell, but I always had to translate function composition "manually" in my head. The pipeline operator just fits the reading direction so much better, and requires less of a "stack" memory in your head. Kind of like how in german, you have to reach the end of the composition before you can understand what happens.
beyphy@reddit
I prefer PowerShell's piping operator which is
|e.g.uptimefordays@reddit
It's just like a bash pipeline but object oriented, it's better than it has any business being!
Ok-Scheme-913@reddit
Yeah, one of the few things Microsoft got right.
At least in principle. They would be better with reverse noun-verb order (you have way more options starting with "Get-" than starting with "File-"), plus all the exceptions and bit unclear closures/flags etc.
Thotaz@reddit
If you know the noun there is nothing stopping you from writing:
*-Noun<Ctrl+Space>to list out all the verbs for that noun. Anyway, they have talked about this and essentially the reason boils down toVerb-Nounbeing easier to read and understand, especially for sysadmins.https://devblogs.microsoft.com/powershell/tab-completion/
https://devblogs.microsoft.com/powershell/verb-noun-vs-noun-verb/
I also think it plays nicely with how module authors typically name modules to ensure they are unique. In many cases they add a known prefix for every command in the module, for example the ActiveDirectory module starts every noun with "AD" like:
Get-ADUser,Get-ADComputer,Get-ADGroupand so on. This means you can type inGet-AD<Ctrl+Space>and get a full list of everything you canGet. With the Noun-Verb pattern it would only show commands that match your noun exactly.thats_a_nice_toast@reddit
If you know Haskell, "modern" syntax features like this look laughable in comparison. It's cool, don't get me wrong, but Haskell does these things properly.
Kered13@reddit
Hard disagree. Haskell uses so many custom operators that it becomes indecipherable line noise. There's nothing wrong with the pipe operator itself, but there is something wrong when you have 30 different varieties of composition operators with God knows what order of precedence.
Jhuyt@reddit
I'm a novice at haskell, but I really like it everytime I tried it
simon_o@reddit
Both operators aren't that useful in languages that have
.though.Jhuyt@reddit
You mean
.as in function composition or as in member access?simon_o@reddit
Member access.
Jhuyt@reddit
Haskell does both function composition and member access with
., which is interesting to me. In my never happening language->would be reserved for member access and.for composition. I think I'm the only one who'd want it like thatsimon_o@reddit
True.
AustinVelonaut@reddit
The pipeline operator
|>here is actually the reverse application operator&in Haskell, distinct from the function composition operator.. I prefer writing uniform left-to-right functional pipelines, so in my language Admiran I have reverse application (|>), reverse composition (.>) and monadic bind (>>=) which can be intermixed in a uniform left-to-right pipeline.techne98@reddit (OP)
I haven't actually written any Haskell (which is criminial considering I'm endorsing functional programming, I know), so I'll have to check it out.
I've really been meaning to give Haskell a shot, but as I'm more of a newbie to FP I've been focusing largely on OCaml thus far (and also enjoy Elixir as you could probably tell from the article haha).
I think the pipe operator in general is nice for me because it helps me model the idea of "input -> data transformation -> output" if that makes sense.
arc_inc@reddit
https://learnyouahaskell.github.io/introduction.html
I’ve heard Learn You a Haskell For Great Good is a great resource.
tonygoold@reddit
Bro, do you even
lift? Just kidding, I am terrible at Haskell despite multiple attempts.techne98@reddit (OP)
Hahaha, I have a feeling I would be as well. I'll probably give it a try soon.
It's hard for me at least, trying to actually learn CS stuff properly after coming from web development, and being self-taught 😅
Jhuyt@reddit
Yeah I think the pipeline operator makes sense too, but somehow I prefer function composition. I'm no hardcore functional programmer so I'm not sure what I'd think if I did more of it
trmetroidmaniac@reddit
The Haskell way is to do what you like. You can use
(.),($)or(&).I'm also rather fond of threading macros in Lisps.
drakythe@reddit
PHP just got the pipe operator in 8.5 and I haven’t had a chance to use it yet, but we use method chaining all the time, so I’m excited to have the option to use a similar setup with functions. Larry Garfield has been really pushing the FP functionality in PHP a lot and while I don’t understand it yet I’m glad to have the paradigm available as technology keeps moving forward.
UnmaintainedDonkey@reddit
The PHP stdlibs however works really poorly with the pipe operator. I find it kind of bad to just bilt it on at a late srage like this.
ZelphirKalt@reddit
This is the curse of languages, that have been badly designed initially. Most of them get stuck with backwards compatibility and having to maintain their old stuff, that doesn't work well with the new stuff and it's overall holding the language back.
PHP now trying to get more functional is laudable. And a pipe operator or way of threading function calls is great to have. Though one thing needs to be kept in mind: It is most useful, when one is threading calls to pure functions, not mere procedures with side effects. PHP is not a languages, that encourages functional style. Its standard library is all about mutating stuff, at least last time I checked, and I have not read or heard much about functional data structures in PHP either.
As usual when such a thing is bolted on instead of the language design adapted to include it and encourage it from first principles, it will work, but probably not be as great as in languages, that were built to support the new thing from the get go.
For example:
Looks nice right? So lets try some things.
Still works, positively surprised ...
Ah too bad. A language that considers functions as first class citizen would probably have supported that. But unfortunately, lots of syntactic clutter is needed. It is working, but it won't win a beauty price. It's PHP.
First it was OOP that was bolted on top, now it is a few things more common in FP first languages. Improves things a little bit, but in either direction doesn't get close to languages doing things from first principles. PHP will likely remain a hodgepodge language.
UnmaintainedDonkey@reddit
Yup. Its just a big ball of mud. No design went into this feature, its yet again a bolt-on, like most of php "new" features. They just copy from (rolls dice) and adds some feature (that usually brings little value) that works with like 40% of the already included stuff.
OMG_A_CUPCAKE@reddit
this works on the other hand, even though it looks weird, and nobody would use it. Just the old "I need a callable" syntax before
(...)and closures where introduceddrakythe@reddit
Which part of the standard libraries does it work poorly with? Again, I haven’t gotten to use it myself, so I’d love some first hand experience about the footguns
UnmaintainedDonkey@reddit
Every function that accepts a random number of arguments. And who knows in what order.
drakythe@reddit
My reading was that is why they're pushing Partial Function Application in 8.6
manifoldjava@reddit
Function *chaining* is not a true analog to the pipe operator. Chaining relies on state in a common `this` the type of which must be returned by each function. As a consequence, call chaining in OOP is constrained by static design:
this.foo().bar().baz() // limited to calls on `this`
The true analog is function call *nesting* where as with piping the output of one function is the input to the other:
baz(bar(foo()))
This inside-out syntax is harder to read than the more natural flow with pipes:
foo |> bar |> baz
drakythe@reddit
This is all true. What I intended to communicate was the pattern goes from inside out to chaining left to right style, as you pointed out. Right now my biggest bugbear with them (and I don’t know how serious of an issue it is) is they don’t seem to have adopted the null safe operator for the pipe system, so object methods can be run like
$result = $object?->methodOne()?->methodTwo?->methodFinal();And if any of the methods would return NULL the chain terminates and $result is null without errors. But it doesn’t look like the pipe operator has that same option
$result = $thing |> doThing() |> doOtherThing() |> doFinalThing()Will throw an error if either NULL or void returns are given before the final function in the chain (since void is “coerced” to be NULL with pipes)
manifoldjava@reddit
Fwiw, functional langs tend to place the onus on the callee to explicitly handle null. Meh. Not the best design.
In my view with PHP we are better off using chaining and nesting. The pipe offers left-to-right flow, but honestly the readability difference is subjective here; a bone thrown to the functional-minded.
drakythe@reddit
It’s also prep for making the functional paradigm more possible in PHP. You can read more about why it’s important and what comes next here https://thephp.foundation/blog/2025/07/11/php-85-adds-pipe-operator/. The Partial Function Application is already approved for 8.6, so we’re now at 2/3rds of what Larry thinks we need for more robust functional features.
Also that blog gives an example of how to handle nullables. So that’s nice. I just need to remember it.
techne98@reddit (OP)
I've never used PHP but that's pretty cool to hear! I'm coming from a web development background, maybe I'll check it out and see how it goes :)
drakythe@reddit
PHP gets lots of shit but it’s been steadily improving for years, in both the language and the ecosystem, and thanks to Wordpress (not my favorite) it still runs a good chunk of the internet. Totally worth knowing if you’re a web dev.
UnmaintainedDonkey@reddit
PHP still has bad (none) unicode and the concurrency model is even worse.
drakythe@reddit
I don’t need concurrency in my day to day work. Or at least not beyond the FPM child process system to run multiple threads at once. So I’ve never really worked on it. I’m still wrapping my brain around fibers and stack handling for stuff like that. But also 8.6 is going to include partial function application, which will allow for some shenanigans when it comes to calling functions without having all the required pieces yet. So that might help (my concurrency chops are woefully limited).
Unicode handling might not be in PHP core, but the mb and intl libs make handling it pretty simple, and those libs are maintained by the PHP foundation, not 3rd parties, so they might as well be core, you just have to turn them on.
lurkinas@reddit
This depends heavily on your level of experience and career goals.
If you're a junior and struggling to break into the market, then hammering away on some WordPress plugins or themes is fine: it's a necessary evil and a wedge into something real. Nobody can rightly blame you. Do it while you still have the window available, AI is already replacing you.
The company whose bread and butter is PHP in 2026 is either an abject failure, in a specific porn niche, only partially maintaining it while moving to anything else with longevity, or otherwise somehow ineptly yet to realize their obvious death spiral.
I'm not trying to be cruel with this: if you're in some weird Stockholm Syndrome rambling about Symfony or whatever, you need to spend the time and move on or your career is toast.
drakythe@reddit
That’s a lot of hyperbole without a lot of backing. Gonna disagree, but thanks for the input.
XenonBG@reddit
That's a bit of a weird opinion to have, at least if you don't support it with some numbers. Php is getting used less and less but it's still one of the largest languages out there.
Besides, Symfony's very similar to Java's Spring Boot, so it's not like you're stuck with php forever if you learn how Symfony works.
eflat123@reddit
It feels like so long ago I was deep into PHP. Is it mostly legacy code on it today? I think it would be fun to port some to something modern, but if it ain't broke...
OMGItsCheezWTF@reddit
I write fintech software for an enormous US tech firm. Lots of it is PHP, mostly modern PHP 8.5 codebases on Symfony 7.4
harmar21@reddit
Laravel and Symfony are 2 of the biggest frameworks. I personally havent used laravel, but symfony is great and what I been using for the past decade professionally.
XenonBG@reddit
I just love Symfony. You can so much with it, while keeping the codebase actually clean because Symfony tries it best to force you to keep it clean.
drakythe@reddit
There is a ton of legacy code lying around, for sure. But there are also lots of modern frameworks that update frequently and are in no way legacy (Laravel is the one I am most familiar with). The CMS Drupal (my primary day job) is updating new major versions every 2 years, with upgrade paths built in. You’ll always find modules or plugins that are lagging and legacy, but honestly if they’re using any modern techniques like composer and PSRs then patching isn’t usually difficult.
techne98@reddit (OP)
Yeah that's a fair statement, and to be honest sometimes keeping up with the JS ecosystem is frankly exhausting.
It's part of the reason I've actually been going more into the CS stuff and away from web dev, but maybe PHP will bring back some of that energy. The web is an awesome platform.
smacke@reddit
Pipe operator for jupyter: https://github.com/smacke/pipescript
CrapsLord@reddit
pipes in linux are so much more than method chaining. Method chaining is a series of synchronous operations, one after the other, with the output from one being supplied as input to the next at completion. A pipe is a mono or even bidirectional communication between two concurrent processes in linux, each with their own PID and environment.
techne98@reddit (OP)
Yeah that’s a fair point, maybe I should’ve covered that more in the post.
I mostly wanted to draw the comparison at least conceptually, and then get into the PL side of things.
SemperVinco@reddit
ITT programmers discover function composition
yawaramin@reddit
And some don't like it.
solve-for-x@reddit
The real fun begins when someone stops to consider what happens when one of the steps in the pipeline can return a nullish or error result, but you don't want to perform a guard check on every step. To paraphrase Emperor Palpatine, function composition is a pathway to many abilities some consider to be unnatural.
Anodynamix@reddit
It's time for a monad, my friend.
solve-for-x@reddit
That's what I mean. At at a certain point people are going to want to put their intermediate values in a box and control the composition logic, and then before you know it you're in a world of monoids and endofunctors.
Anodynamix@reddit
I mean then maybe you shouldn't use a pipeline for that work then.
Every tool has its place. Some people will abuse pipelines. Doesn't make them a bad tool though.
runevault@reddit
Adding to the above, if any readers want to learn about what these two are discussion there's a great article from an F# point of view on "railway oriented programming"
https://fsharpforfunandprofit.com/rop/
EliSka93@reddit
Nullability operators in C# very elegantly solve that.
psi-@reddit
I don't see how's that different from the non-chained version. Sure you need machinery around all that, but this kind of encourages reusability (or rather plugability) instead of case-by-case error resolution handling
georgehotelling@reddit
https://xkcd.com/1053/
techne98@reddit (OP)
Yeah, it's pretty cool to learn about!
mccoyn@reddit
I don’t like it. What this (and many functional features) does is give programmers an opportunity to do something without picking a name for the intermediate values. Those names are quite valuable when trying to read code later.
yawaramin@reddit
Programmers already have the 'opportunity' to not pick names for intermediate values:
h(g(f(x))). That's just normal function call syntax. You have that whether you're using a functional programming language or not. The pipe operator at least lets you visualize the data flow:x |> f |> g |> h.As always, it's up to the programmer's good judgment whether intermediate named variables are needed or not. No language or paradigm can replace that.
kevinb9n@reddit
Those names are sometimes valuable when trying to read code later.
When they are, then don't use a pipeline operator.
foxsimile@reddit
Excellently put.
Nothing is stopping people from nesting function calls like a motherfucker with or without the pipeline operator:
People who are dogshit at naming things will find a way regardless of what operators they have at their disposal. So long as they can name an identifier, they’ll find a way to make it make as little sense as possible.
beyphy@reddit
Are you saying that my variable name
tempTempyisnt' valuable? /s.techne98@reddit (OP)
Genuine question: why would you need names for the intermediate values?
If your goal is to transform input into a certain output, and the path to which that achieved is clear, why not use the pipe operator?
Or are you suggesting that both the method chaining example in the post and the pipe example are both wrong, and instead it's more ideal to just split everything up into separate variables?
Urik88@reddit
The intermediate value name can be self documentation for why one of these functions in the middle of the pipe operator was needed.
I do wish we had it in Typescript many times, but I can see his point
wisemanofhyrule@reddit
I've found that function naming is generally sufficient for describing what is going on. With pipelining its easy to describe each part of the process as an individual function. Which gives you the secondary bonus of making each function smaller and easier to test.
lelanthran@reddit
There's a trade-off; GP perhaps would like more clarity about what the intermediate steps mean when reading code, but your example is basically the best-case scenario for chaining (whether you are chaining via an operator or method calls is irrelevant to him, I would think).
I can easily imagine a case of (for example):
This makes comprehension difficult, debugging almost impossible and logging actually impossible.
What if
myDatawas data from outside the program (fetchcall, user input, etc) and we got the wrong data? All we see is an exception.What shape does
constrain()result in? Is it a table? Is it a tree? Something else?What does
normalise()result in? Is it fixing up known data errors? Is it checking references are valid?All we really know is what
deduplicate()returns.We cannot log the time between each step, even temporarily. We cannot log the result of each step. We can't introduce unwinding steps if this is stateful.
This differs a lot from the best-case scenario you present, and to be fair, your example is the most common type of usage for this sort of thing, and I wouldn't hesitate to use it in production. What I would not do is choose a chained approach for functions/methods that are not standard.
TankorSmash@reddit
Surely you can!
becomes
where
printis a function that dumps its args to stdout and returns it.Kered13@reddit
That would be a very surprising print function.
TankorSmash@reddit
Depends on the language for sure, but if you're doing a lot of chaining like this, and don't have access to the
|>operator to slot in arbitrary generic functions, putting this on your class is great.Norphesius@reddit
Have the functions log the errors, or maybe even have them return a Result type, monad style.
Obviously this is a general example, but these methods take a one/two thing in, and spit one thing out, so I'm not sure how you're supposed to clarify those intermediary steps with more info, outside of literally saying what the method is doing in particular. With context, if this was particular data for a particular purpose, sure, add an intermediary name if you want, but if we're just dealing with generic "data" or that context is already provided elsewhere (e.g. the surrounding function) you would just have code like this:
If you need to log the result of each step or unwind, then split it up and do that, but if you don't need to do that, then just chain them. Its fine.
uJumpiJump@reddit
You make it sound like code is immutable and cannot be modified
EarlMarshal@reddit
To train your word choice intuition. We need more tempVarIntermediateValue and stuff. /s
ykafia@reddit
Why use more words when few words do trick
Versaiteis@reddit
Seriously, what is this, HLSL?
It would be
tmpvarintvalWilling_Monitor5855@reddit
Pls show some respect to Hungarian Notation. crszkvc30tempVarIntermediateValue. Anyone who can't tell from the name shouldn't be programming anything bar laundry cycles.
ryosen@reddit
Seriously, Hungarian Notation just makes everything so much clearer. For instance, your variable
crszkvc30tempVarIntermediateValue. This is clearly a temporary variable whose intent is to be used as an intermediary value between operations on a temporary basis, whose length is fixed to 30 characters and whose valid range of values are exclusively limited to words in Polish.mccoyn@reddit
What I often see, is that it isn't entirely obvious what the individual piping steps do. That is because a function is used in a way that doesn't explicitly match its function name. Also, I see large number of arguments for individual steps that make it difficult to follow the piping (which can be addressed with whitespace usage).
The result is that piping is only clear when things are sufficiently simple (and always looks good in sample code). But, my experience, is things get more complicated over time, such as arguments added to functions. So, piping will eventually become unclear, at least in a large long-living project.
I have the same reservations about chaining.
I will say that there are some cases where the function of the intermediate values is very obvious and piping does remove some unnecessary verbosity.
Kered13@reddit
It's one of those things that has to be used in moderation. Completely unchained code can be harder to read because of all the useless intermediate variables. But excessive chaining can be hard to read because it's easy to lose teach of what's going on. It can be good to break up the chain at major milestones to provide a sort of mental checkpoint.
techne98@reddit (OP)
I do so where you're coming from, yeah I imagine it's something where it's like "it depends". Some other commenters also gave some good pushback on when you should/shouldn't use it.
Appreciate the response regardless, hope my initial comment didn't come off too abrasive :)
jandrese@reddit
For one it is documentation for people trying to understand your code later. But the big thing is that when something in that big pipe isn't working it can be very difficult to track down where the error is happening when everything is anonymous. Having the intermediate values split out also allows you to inspect the contents of those temporary variables to see where something has gone wrong.
rlbond86@reddit
It does help debugging sometimes, but you can also just log things out as intermediate steps.
Ahhhhrg@reddit
I’m actually of the complete opposite opinion. The great value is precisely that you’re not forced to come up with bogus intermediate names. Famously, “There are only two hard things in Computer Science: cache invalidation and naming things.”.
wasdninja@reddit
It puts requirements on the function names but that's true already pretty much. Stuff like this shouldn't surprise any developer
And that's functional right now.
pip25hu@reddit
Fair, though the functions invoked via the pipe operator could still have useful, descriptive names.
syklemil@reddit
And language servers can provide inline hints for what the types are. That doesn't help reviewers who rely on just reading the diff, unless we get language server-powered diffing tools.
flanger001@reddit
I do like to say “Ruby developers type an equals sign challenge 202x”
denarii@reddit
I shan't.
gyp_casino@reddit
The author uses Elixir as an example of a functional language with pipes, and it seems interesting, but R is a more notable example (#9 on the Tiobe index vs. Elixir's #42).
Here is the R code for the proposed operation (string uppercase, split, reverse, flatten).
makotech222@reddit
This is so funny cause it looks so much worse to me, and also devex is worse as well.
Now, i may not be a big city programmer, but when I call "test".ToUpper(), My intellisense will autocomplete the method call as I'm typing it, and also give me the entire list of possible methods to call on this instance of a string. It also gives me the return type, so I know if the method modifies the string or returns a new one.
chuch1234@reddit
I mean intellisense should be able to handle the pipe operator just as well, it still knows what the functions and types are
Kered13@reddit
Usually recommendations on free functions are less useful, because there are a lot more names in the scope.
chuch1234@reddit
Good point!
makotech222@reddit
The pipe operator breaks the typing, i assume it also doesn't understand the context of its call as much. You have to type out the 'String.' manually before you get only string specific methods. C# also includes extension methods and inherited methods, that wouldn't show up on static 'String.' calls.
Ok-Scheme-913@reddit
That's false. If you have an expression like f(a) | g | h
then by the time you type out the pipe operator, you know the return type of the previous part and can offer only relevant functions that take that type as a parameter.
makotech222@reddit
does the ide intellisense autocomplete after pressing '|' + 'Space'? i imagine it doesnt
TiF4H3-@reddit
It's going to depend on the LSP, but any IDE worth its salt has a keybind to activate intellisense completion (
C-xfor me, on Helix).z500@reddit
Actually in F# using piping often gives it enough context to infer the type, which means you can just drop the type from the function signature. This gives it a less noisy, mathematical look.
chuch1234@reddit
What ide and language?
willehrendreich@reddit
it's a beautiful thing.
solvedproblem@reddit
I've used it once since php got it in 8.5 and it looks pretty sweet written out. It's highly contextual though. I'll use it when appropriate though.
CuTTyFL4M@reddit
I'm new to web development and I've been working on a project with Symfony, so I've been handling PHP for a while now - and I like it!
No idea what this means, can someone explain the bigger picture?
Finchyy@reddit
I'd be interested to see this sort of thing in Ruby. We already have the rocket operator
=>to spaff out the contents of a hash into a variable, so I think the same operator could be used for this as they're semantically similar imoBut perhaps this is what block yielding is for
vancha113@reddit
Ah, short read, but yes! Pipe operators are very neat :) makes things very readable.
techne98@reddit (OP)
Yeah I was actually thinking that myself when I wrote it 😅 and indeed haha
germanheller@reddit
the pipeline operator is one of those features that makes you realize how much mental overhead nested function calls actually have. reading
result = h(g(f(x)))vsx |> f |> g |> his like reading a sentence backwards vs forwards.used it heavily in elixir and going back to JS where i have to chain
.then()or nest calls felt painful. the TC39 proposal has been stuck for years tho and at this point im not sure itll ever land in vanilla JS. typescript could adopt it independently but they historically avoid syntax that doesnt exist in JS.for now i just use small pipeline helper functions. not as pretty but gets the job done without waiting for committee consensus
Frolo_NA@reddit
smalltalk:
devraj7@reddit
Because you picked a trivial example.
Try again with methods that need more than one parameter and you'll see weird syntax emerge, even in Smalltalk.
Frolo_NA@reddit
what kind of example do you need? cascade becomes the elegant solution for repeated message sends and you don't care much if they have multiple parameters or not
MadCervantes@reddit
Method chaining suuuucks. It relies on implicit return behavior. Pipelines are more modular and explicit.
aclima@reddit
There are dozens of us! Dozens! https://aclima93.com/custom-functional-programming-operators
-BunsenBurn-@reddit
The pipe operator in R is my goat.
I love being able to perform the data transformations/cleansing I want using tidyverse and then be able to pipe it into a ggplot