[HN Gopher] PHP 8.5 adds pipe operator
___________________________________________________________________
PHP 8.5 adds pipe operator
Author : lemper
Score : 410 points
Date : 2025-08-05 04:13 UTC (18 hours ago)
(HTM) web link (thephp.foundation)
(TXT) w3m dump (thephp.foundation)
| bapak wrote:
| Meanwhile the JS world has been waiting for 10 years for this
| proposal, which is still in stage 2
| https://github.com/tc39/proposal-pipeline-operator/issues/23...
| wouldbecouldbe wrote:
| It's really not needed, syntax sugar. With dots you do almost
| the same. Php doesn't have chaining. Adding more and more
| complexity doesn't make a language better.
| EGreg wrote:
| It's not really chaining
|
| More like thenables / promises
| wouldbecouldbe wrote:
| It looks like chaining, but with possibility of adding
| custom functions?
| bapak wrote:
| It's chaining without having to vary the return of each
| function. In JS you cannot call 3.myMethod(), but you
| could with 3 |> myMethod
| cyco130 wrote:
| It requires parentheses `(3).myMethod()` but you can by
| monkey patching the Number prototype. Very bad idea, but
| you absolutely can.
| EGreg wrote:
| Not only that
|
| In chaining, methods all have to be part of the same
| class.
|
| In C++ we had this stuff ages ago, it's called abusing
| streaming operators LMAO
| bapak wrote:
| Nothing is really needed, C89 was good enough.
|
| Dots are not the same, nobody wants to use chaining like
| underscore/lodash allowed because it makes dead code
| elimination impossible.
| pjmlp wrote:
| K&R C was good enough for UNIX System V, why bother with
| C89.
| boobsbr wrote:
| K&R C was the apex, we've just been going downhill since.
| lioeters wrote:
| This but unironically.
| troupo wrote:
| > With dots you do almost the same.
|
| Keyword: _almost_. Pipes don 't require you to have many
| different methods on every possible type:
| https://news.ycombinator.com/item?id=44794656
| te_chris wrote:
| Dots call functions on objects, pipe passes arguments to
| functions. Totally missing the point.
| Martinussen wrote:
| When you say chaining, do you mean autoboxing primitives? PHP
| can definitely do things like `foo()->bar()?->baz()`, but
| you'd have to wrap an array/string yourself instead of the
| methods being pulled from a `prototype` to use it there.
| chilmers wrote:
| I'm tired of hearing the exact same arguments, "not needed",
| "just syntax sugar", "too much complexity", about every new
| syntax feature that gets added to JS. Somehow, once they are
| in the language, nobody's head explodes, and people are soon
| using them and they become uncontroversial.
|
| If people really this new syntax will make it harder to code
| in JS, show some evidence. Produce a study on solving
| representative tasks in a version of the language with and
| without this feature, showing that it has negative effects on
| code quality and comprehension.
| robertlagrant wrote:
| Presumably it's up to the change proposers to produce said
| study showing the opposite.
| purerandomness wrote:
| If your team prefers not to use this new optional feature,
| just enable a PHPStan rule in your CI/CD pipeline that
| prevents code like this getting merged.
| hajile wrote:
| Chaining requires creating a class and ensuring everything
| sticks to the class and returns it properly so the chain
| doesn't blow up. As you add more options and do more stuff,
| this becomes increasingly hard to write and maintain.
|
| If I'm using a chained library and need another method, I
| have to understand the underlying data model (a leaky
| abstraction) and also must have some hack-ish way of
| extending the model. As I'm not the maintainer, I'm probably
| going to cause subtle breakages along the way.
|
| Pipe operators have none of these issues. They are obvious.
| They don't need to track state past the previous operator
| (which also makes debugging easier). If they need to be
| extended, look at your response value and add the appropriate
| function.
|
| Composition (whether with the pipe operator or not) is vastly
| superior to chaining.
| lacasito25 wrote:
| in typescript we can do this
|
| let res res = op1() res = op2(res.op1) res = op3(res.op2)
|
| type inference works great, and it is very easy to debug and
| refactor. In my opinion even more than piping results.
|
| Javascript has enough features.
| avaq wrote:
| Not only have we been waiting for 10 years, the most likely
| candidate to go forward is not at all what we wanted when the
| proposal was created:
|
| We wanted a pipe operator that would pair well with unary
| functions (like those created by partial function application,
| which could get its own syntax), but that got rejected on the
| premise that it would lead to a programming style that utilizes
| too many closures[0], and which could divide the ecosystem[1].
|
| Yet somehow PHP was not limited by these hypotheticals, and
| simply gave people the feature they wanted, in exactly the form
| it makes most sense in.
|
| [0]: https://github.com/tc39/proposal-pipeline-
| operator/issues/22... [1]: https://github.com/tc39/proposal-
| pipeline-operator/issues/23...
| lexicality wrote:
| Am I correct in my understanding that you're saying that the
| developers of the most widely used JS engine saying "hey we
| can't see a way to implement this without tanking
| performance" is a silly hypothetical that should be ignored?
| avaq wrote:
| They can't implement function application without tanking
| performance? I find that hard to believe. Especially
| considering that function application is already a commonly
| used (and, dare I say: essential) feature in the language,
| eg: `Math.sqrt(2)`.
|
| All we're asking for is the ability to rewrite that as `2
| |> Math.sqrt`.
|
| What they're afraid of, my understanding goes, is that
| people _hypothetically_ , may start leaning more on
| closures, which themselves perform worse than classes.
|
| However I'm of the opinion that the engine implementors
| shouldn't really concern themselves to that extent with how
| people write their code. People can always write slow code,
| and that's their own responsibility. So I don't know about
| "silly", but I don't agree with it.
|
| Unless I misunderstood and somehow doing function
| application a little different is actually a really hard
| problem. Who knows.
| nilslindemann wrote:
| Your simple example (`2 |> Math.sqrt`) looks great, but
| when the code gets more complex, then the advantage of
| the pipe syntax is less obvious. For example,
| foo(1, bar(2, baz(3)), 3)
|
| becomes something like 1 (2, (3 |> baz)
| > bar), 3 |> foo
|
| or (3 |> baz) |> (2, % |> bar) |> (1,
| %, 3 |> foo)
|
| That looks like just another way to write a thing in
| JavaScript, and it is not easier to read. What is the
| advantage?
| avaq wrote:
| Uhm, don't do it, then. That's like arguing that the
| addition of the ternary operator is a bad one because not
| all if/else blocks look better when translated into it.
|
| The goal is to linearlize unary function application, not
| to make all code look better.
| sir_eliah wrote:
| I think the commenter meant that once the new syntax is
| approved and adopted by the community, you have no choice
| to not use the syntax. You'll eventually change your
| project and will be forced to deal with reviewing this
| code.
| jeroenhd wrote:
| With JS' async/await system basically running on creating
| temporary closures, I don't think things will change all
| that much to be honest.
|
| Furthermore, I don't see why engines should police what is
| or isn't acceptable performance. Using functional
| interfaces (map/forEach/etc.) is slower than using for
| loops in most cases, but that didn't stop them from
| implementing those interfaces either.
|
| I don't think there's that much of a performance impact
| when comparing const x = fun1(abc);
| const y = fun2(x); const z = fun3(y);
| fun4(z);
|
| and abc |> fun1 |> fun2 |> fun3 |> fun4
|
| especially when you end up writing code like
| fun1(abc).then( (x) => fun2(x)).then( (y) =>
| fun3(y)).then((z) => fun4(z))
|
| when using existing language features.
| ufo wrote:
| The problem they were discussion in the linked Github
| issue are pipelines where the functions receive more than
| one argument. const x = fun1(a, 10)
| const y = fun2(x, 20) const z = fun3(y, 30)
|
| In this case the pipeline version would create a bunch of
| throwaway closures. a |> ((a) =>
| fun1(a, 10)) |> ((x) => fun2(x, 20))
| |> ((y) => fun3(y, 30))
| hajile wrote:
| Closures won over OOP in Javascript a long time ago (eg,
| React switching from classes to functions + closures), but
| they still keep trying to force garbage like private
| variables on the community.
|
| Loads of features have been added to JS that have worse
| performance or theoretically enable worse performance, but
| that never stopped them before.
|
| Some concrete (not-exhaustive) examples:
|
| * Private variables are generally 30-50% slower than non-
| private variables (and also break proxies).
|
| * let/const are a few percent slower than var.
|
| * Generators are slower than loops.
|
| * Iterators are often slower due to generating garbage for
| return values.
|
| * Rest/spread operators hide that you're allocating new
| arrays and objects.
|
| * Proxies cause insane slowdowns of your code.
|
| * Allowing sub-classing of builtins makes everything slow.
|
| * BigInt as designs is almost always slower than the
| engine's inferred 31-bit integers.
|
| Meanwhile, Google and Mozilla refuse to implement proper
| tail calls even though they would INCREASE performance for
| a lot of code. They killed their SIMD projects (despite
| having them already implemented) which also reduced
| performance for the most performance-sensitive
| applications.
|
| It seems obvious that performance is a non-issue when it's
| something they want to add and an easy excuse when it's
| something they don't want to add.
| tracker1 wrote:
| I wish I could upvote this more than once. I really liked
| the F# inspired pipe operator proposal and even used it a
| bit when I used to lean on 6to4/babel more, but it just
| sat and languished forever it seems. I can't really think
| of any other language feature I've seen since that I
| would have wanted more. The new Temporal being one
| exception.
| hajile wrote:
| It seems like none of the really good language proposals
| have much traction. Proper Tail Calls have been in the
| language for TEN YEARS now, but v8 and Spidermonkey still
| violate the spec and refuse to implement for no good
| reason.
|
| Record/tuple was killed off despite being the best
| proposed answer for eliminating hidden class mutation,
| providing deep O(1) comparisons, and making
| webworkers/threads/actors worth using because data
| transfer wouldn't be a bottleneck.
|
| Pattern matching, do expressions, for/while/if/else
| expressions, binary AST, and others have languished for
| years without the spec committee seemingly caring that
| these would have real, tangible upsides for devs and/or
| users without adding much complexity to the JIT.
|
| I'm convinced that most of the committee is completely
| divorced from the people who actually use JS day-to-day.
| jedwards1211 wrote:
| I think it's mainly because they struggled to get
| consensus on which syntax to go with for pipelines, since
| people were divided into three different camps. I wish
| they would just standardize all three options with a
| slightly different operator for each one
| int_19h wrote:
| > * Rest/spread operators hide that you're allocating new
| arrays and objects.
|
| Only in function calls, surely? If you're using spread
| inside [] or {} then you already know that it allocates.
| hajile wrote:
| It used to be said that "Lisp programmers know the value
| of everything and the cost of nothing."
|
| This applies to MOST devs today in my experience and
| doubly to JS and Python devs as a whole largely due to a
| lack of education. I'm fine with devs who never went to
| college, but it becomes an issue when they never bothered
| to study on their own either.
|
| I've worked with a lot of JS devs who have absolutely no
| understand of how the system works. Allocation and
| garbage collection are pure magic. They also have no
| understanding of pointers or the difference between the
| stack and heap. All they know is that it's the magic that
| makes their code run. For these kinds of devs, spread
| just makes the object they want and they don't understand
| that it has a performance impact.
|
| Even among knowledgeable devs, you often get the argument
| that "it's fast enough" and maybe something about
| optimizing down the road if we need it. The result is a
| kind of "slow by a thousand small allocations" where your
| whole application drags more than it should and there's
| no obvious hot spot because the whole thing is one giant,
| unoptimized ball of code.
|
| At the end of the day, ease of use, developer ignorance,
| and deadline pressure means performance is almost always
| the dead-last priority.
| nobleach wrote:
| I know that was the reasoning for the Records/Tuples
| proposal being shot down. I haven't dug too deeply into the
| pipeline operators other than to get a feel for both
| proposals.
|
| Most of the more interesting proposals tend to languish
| these days. When you look at everything that's advanced to
| Stage 3-4, it's like. "ok, I'm certain this has some
| amazing perf bump for some feature I don't even use... but
| do I really care?"
| xixixao wrote:
| I guess partially my fault, but even in the article, you can
| see how the Hack syntax is much nicer to work with than the
| functional one.
|
| Another angle is "how much rewriting does a change require",
| in this case, what if I want to add another argument to the
| rhs function call. (I obv. don't consider currying and point-
| free style a good solution)
| WorldMaker wrote:
| I am wondering if PHP explicitly rejecting Hack-style pipes
| (especially given the close ties between PHP and Hack, and
| that PHP _doesn 't_ have partial application, but JS does,
| sort of, though its UX could be improved) will add leverage
| to the F#-style proposal over the Hack-style.
|
| It may be useful data that the TC-29 proposal champions can
| use to fight for the F# style.
| fergie wrote:
| Good- the [real world examples of pipes in
| js](https://github.com/tc39/proposal-pipeline-
| operator?tab=readm...) are deeply underwhelming IMO.
| defraudbah wrote:
| do not let me start on monads in golang...
|
| both are going somewhere and super popular though
| 77pt77 wrote:
| Best you'll git will be 3 new build systems and 10 new
| frameworks
| epolanski wrote:
| Sadly they went obsessing over pipes with promises which don't
| fit the natural flow.
|
| Go explain them that promises already have a natural way to
| chain operations through the "then" method, and don't need to
| fit the pipe operator to do more than needed.
| sandreas wrote:
| While I appreciate the effort and like the approach in general,
| in this use case I really would prefer extensions / extension
| functions (like in Kotlin[1]) or an IEnumerable / iterator
| approach (like in C#). $arr = [ new
| Widget(tags: ['a', 'b', 'c']), new Widget(tags: ['c',
| 'd', 'e']), new Widget(tags: ['x', 'y', 'a']), ];
| $result = $arr |> fn($x) => array_column($x, 'tags') //
| Gets an array of arrays |> fn($x) => array_merge(...$x)
| // Flatten into one big array |> array_unique(...)
| // Remove duplicates |> array_values(...)
| // Reindex the array. ;
|
| feels much more complex than writing $result =
| $arr->column('tags')->flatten()->unique()->values()
|
| having array extension methods for column, flatten, unique and
| values.
|
| 1: https://kotlinlang.org/docs/extensions.html#extension-
| functi...
| troupo wrote:
| The advantage is that pipes don't care about the type of the
| return value.
|
| Let's say you add a reduce in the middle of that chain. With
| extension methods that would be the last one you call in the
| chain. With pipes you'd just pipe the result into the next
| function
| sandreas wrote:
| Yeah, I agree. That's an advantage of pipes - although much
| harder to read and write than chained methods in my opinion.
|
| The use-case in the article could still be solved easier with
| extension methods in my opinion :-)
| troupo wrote:
| Yeah, the examples should also show that you can use
| arbitrary functions, not just library functions. E.g. your
| own business logic, validation etc.
| cess11 wrote:
| PHP has traits, just invent that API, put it in a trait and add
| it to your data classes.
| stefanfisk wrote:
| How would that work if a library supplies a function that
| takes a string and returns an array? I can't make it use my
| array class.
| cess11 wrote:
| Could you give a realistic example?
|
| The PHP pipes as described in the articles about it will
| require a bunch of wrapping anyway so you could just do
| that. There are several alternatives, from a function or
| method that just converts from raw array to class, to
| abstractions involving stuff like __invoke, __call,
| dispatchers and such.
|
| Also the expectation to not have to put facades on
| libraries is a bit suspicious, in my experience it is very
| common. I find it unlikely you actually want to use raw
| arrays instead of leveraging type guards in your code.
| stefanfisk wrote:
| If I use Laravel's Str in an app where both myself and a
| third party library want to add a chainable method, how
| do I make the API make sense? We can't both subclass Str.
| The only option I see is Macroable and that's a rabbit
| hole I'd rather avoid.
| sandreas wrote:
| Well, while traits might be a workaround for certain use
| cases, simple arrays with scalar data types could not be
| extended via traits.
|
| While I know that there are Collection classes in Symfony,
| Laravel, etc., I'm not a huge fan of wrapping a PHP array
| with a class to get method chaining, even with generators.
| $sum = [1, 2, 3]->filter(fn($x) => $x%2!=
| 0)->concat([5,7])->sum();
|
| cannot be solved with traits. Additionally, I think traits
| should be used very carefully and they do not have this many
| use cases that aren't a code smell to me.
| hn8726 wrote:
| Kotlin also has extensions function `let` (and a couple of
| variants) which let you chain arbitrary methods:
|
| ``` val arr = ... val result = arr .let { column(it, "tags")
| .let { merge(it) } .let { unique(it) } .let { values(it) } ```
|
| You add function references for single-argument functions too:
|
| ``` arr.let(::unique) // or (List<>::unique), depends on the
| function ```
|
| all without adding a special language construct.
| Contortion wrote:
| Basically what Collections do in Laravel.
| sandreas wrote:
| Exactly... similar in Symfony.
|
| While converting arrays to collection-object is a suitable
| option that does work, it would feel much more "native", if
| there were extension methods for Iterable / Traversable.
| librasteve wrote:
| raku has had feed operators like this since its inception
| # pipeline functional style (1..5) ==> map { $_ * 2
| } ==> grep { $_ > 5 } ==> say(); #
| (6 8 10) # method chain OO style (1..5)
| .map( * * 2) .grep( * > 5) .say;
| # (6 8 10)
|
| uses ==> and <== for leftward
|
| true it is syntax sugar, but often the pipe feed is quite useful
| to make chaining very obvious
|
| https://docs.raku.org/language/operators#infix_==%3E
| gbalduzzi wrote:
| I like it.
|
| I really believe the thing PHP needs the most is a rework of
| string / array functions to make them more consistent and chain
| able. Now they are at least chainable.
|
| I'm not a fan of the ... syntax though, especially when mixed in
| the same chain with the spread operator
| noduerme wrote:
| Agree, the ... syntax feels confusing when each fn($x) in the
| example uses $x as the name of its argument.
|
| My initial instinct would be to write like this:
|
| `$result = $arr |> fn($arr) =>
| array_column($arr, 'tags') // Gets an array of arrays
| |> fn($cols) => array_merge(...$cols)`
|
| Which makes me wonder how this handles scope. I'd imagine the
| interior of some chained function can't reference the input
| $arr, right? Does it allow pass by reference?
| Einenlum wrote:
| You can write it this way. The parameter name is arbitrary.
| And no, to my knowledge you can't access the var from the
| previous scope
| cess11 wrote:
| You can do function ($parameter) use
| ($data) { ... }
|
| to capture stuff from the local environment.
|
| Edit: And you can pass by reference: >
| $stuff = [1] = [ 1, ]
| > $fn = function ($par) use (&$stuff) { $stuff[] = $par; }
| = Closure($par) {#3980 ...2} > $fn(2) =
| null > $stuff = [ 1,
| 2, ]
|
| Never done it in practice, though, not sure if there are any
| footguns besides the obvious hazards in remote mutation.
| colecut wrote:
| PHP string / array functions are consistent.
|
| string functions use (haystack, needle) and array functions use
| (needle, haystack)
|
| because that's the way the underlying C libraries also worked
| Einenlum wrote:
| They're not though.
|
| array_filter takes (arr, callback)
|
| https://www.php.net/manual/en/function.array-filter.php
|
| array_map takes (callback, arr)
|
| https://www.php.net/manual/en/function.array-map.php
| exasperaited wrote:
| This is "english-sentence-order-consistent", as it goes.
|
| Array filter is "filter this array with this function".
|
| Array map is "map this function over this array".
|
| But I agree any replacement function should be consistent
| with Haskell.
| eurleif wrote:
| One can construct English sentences in the opposite
| order. There is no singular "English sentence order".
|
| "Filter for this function in this array"
|
| "Map over this array with this function"
| exasperaited wrote:
| Right, but these are both more unwieldy.
|
| One filters something with something else, in the real
| world. Filter water with a mesh etc.
|
| And (in maths, at least) one maps something onto
| something else. (And less commonly one maps an area onto
| paper etc.)
|
| Just because you can make your two sentences does not
| make them natural word order.
| hnlmorg wrote:
| When you consider that PHP is used by hundreds of
| thousands of non-native English speakers, I don't really
| think you can make a legitimate claim that "English
| sentence order" trumps "consistent argument ordering".
|
| There's enough viral videos online of how even
| neighbouring European counties order common sentences
| differently. Even little things like reading the time
| (half past the previous hour vs half to the next hour)
| and counting is written differently in different
| languages.
|
| So modelling the order of parameters based on English
| vernacular doesn't make a whole lot of sense for
| programming languages used by programmers of all
| nationalities.
| exasperaited wrote:
| > When you consider that PHP is used by hundreds of
| thousands of non-native English speakers, I don't really
| think you can make a legitimate claim that "English
| sentence order" trumps "consistent argument ordering".
|
| Well that's good, because I didn't.
| quietbritishjim wrote:
| > And (in maths, at least) one maps something onto
| something else.
|
| Yes, but that's the opposite of what you said earlier.
| You might map x onto 2*x, for example. Or, if you're
| talking about a collection, you might map the integers
| 0..10 on to double their value. Data first, then the way
| you're manipulating it. I'm a mathematician and this is
| what makes sense to me.
|
| I would only say "map this function..." if the function
| itself is being manipulated somehow (mapped onto some
| other value).
| goykasi wrote:
| But thats not correct. array_map is variadic. So it
| should actually be "Map over these arrays with this
| function."
|
| When you use the correct verbiage, the parameter order
| makes sense.
| goykasi wrote:
| array_map is variadic. It is actually (callback, ...arr)
|
| One function works against a single element, whereas the
| other works against multiple. In that case, the parameter
| order is more meaningful. You can use array_walk if you
| want (arr, callback), but that only works against a single
| array -- similarly to array_filter.
| Y-bar wrote:
| > because that's the way the underlying C libraries also
| worked
|
| I feel like this is a weak defence of the internally
| inconsistent behaviour. As someone who has been programming
| with PHP for over twenty years now, most of them
| professionally, I still cannot remember the needle/haystack
| order in these functions, I thank intellisense for keeping me
| sane here.
|
| As evident with this pipe operator, or with for example
| Attributes, PHP does not need to religiously follow the C way
| of doing things, so why not improve it instead of dismissing
| it as "it is the way it is because that is the way it was"?
| chuckadams wrote:
| It's not so much a defense as it is an explanation of the
| historical origins. Even the creator of the language
| doesn't defend the inconsistencies and admits that they
| were a mistake. PHP also takes backward compatibility
| pretty seriously and doesn't rearrange things for
| consistency's sake alone.
| account42 wrote:
| So they are consistent because they are consistently
| inconsistent??
|
| There isn't a good reason for PHP to have inherited C's
| issues here.
| goykasi wrote:
| In the early days of PHP, it relied heavily on wrapping the
| underlying C libraries and preserving their naming
| conventions.
|
| https://news-web.php.net/php.internals/70950
| gbalduzzi wrote:
| And that was fine in the early days, absolutely.
|
| We are not in the early days though, and in many other
| aspects PHP evolved greatly.
| goykasi wrote:
| What are the benefits? Code completion, AI agents, etc
| will handle it for you. No one's life is falling apart
| because the param ordering is more similar to C than a
| blog article complaining about it decade ago. Php devs
| have had up 30 years to learn the difference. Are C devs
| complaining about this?
|
| If we want to change the param order of str/array
| functions for php, I think we should start with fixing
| the C libraries. That seems like a better starting point.
| The impact will certainly be more beneficial to even more
| developers than just php.
| gbalduzzi wrote:
| Because it would be more predictable, easier to memorize,
| less verbose, easier to use for developers coming from
| other modern languages and more comfortable to work with.
|
| The fact that they are chaotic since 30 years ago is not
| a valid reason for keeping them chaotic right now.
|
| Also, I'm not even arguing they should change the
| existing functions, that would break all existing code
| for almost no reason.
|
| I think they should "simply" support methods on
| primitives, and implement the main ones in a chainable
| way:
|
| "test string"->trim()->upper()->limit(100);
|
| [0,1,2]->filter(fn ($n) => $n % 2 === 0)->map(fn($n) =>
| $n * 2);
|
| I would love this so much
| gbalduzzi wrote:
| `strlen`, `strncmp` and `strtolower` but `str_split` and
| `str_contains`.
|
| How it that consistent?
| account42 wrote:
| The syntax could be improved by allowing you to omit the (...)
| part entirely for single argument functions and using currying
| for functions that need additional arguments. So you would end
| up with something like: $result = $arr
| |> select_column('tags') // Gets an array of arrays
| |> fn($x) => array_merge(...$x) // Flatten into one big array
| |> array_unique // Remove duplicates
| |> array_value // Reindex the array.
| keyle wrote:
| C'mon Dart! Follow up please. Go is a lost cause...
| tayo42 wrote:
| I feel like a kindergartener writing go. I wish another
| language got popular in the space go is used for.
| jillesvangurp wrote:
| Kotlin is shaping up slowly. It's kind of there with a native
| compiler that is getting better with each release and decent
| multiplatform libraries. It's a bit weak with support for
| native libraries and posix stuff. But that's a fixable issue;
| it just needs more people working on that.
|
| For example ktor (one of the server frameworks) can actually
| work with Kotlin native but it's not that well supported.
| This is not using Graal or any of the JVM stuff at runtime
| (which of course is also a viable path but a lot more
| heavyweight). With Kotlin native, the Kotlin compiler
| compiles directly to native code and uses multiplatform
| libraries with native implementations. There is no Java
| standard library and none of the jvm libraries are used.
|
| The same compiler is also powering IOS native with Compose
| multiplatform. On IOS libraries are a bit more comprehensive
| and it's starting to become a proper alternative to things
| like flutter and react native. It also has pretty decent
| objectc and swift integration (both ways) that they are
| currently working on improving.
|
| In any case, it's pretty easy to write a command line thingy
| in Kotlin. Use Klikt or similar for command line argument
| parsing.
|
| Jetbrains seems to be neglecting this a bit for some reason.
| It's a bit of a blind spot in my view. Their wasm support has
| similar issues. Works great in browsers (and supported with
| compose as well) but it's not a really obvious choice for
| serverless stuff or edge computing just yet; mainly because
| of the library support.
|
| Swift is a bit more obvious but has the issue that Apple
| seems to think of it as a library for promoting vendor lockin
| on their OS rather than as a general purpose language. Both
| have quite a bit of potential to compete with Go for system
| programming tasks.
| phplovesong wrote:
| The stdlib is so inconsistent this will be a nightmare.
|
| Optionally with a better language you know what order params as
| passed (array_map / array_filter), but in PHP its an coin toss.
|
| This feels very bolted on and not suited for the stdlib at all.
|
| PHP devs should instead FIRST focus on full unicode support (no,
| the mb_real_uppercase wont do), and only then focus on a new
| namespaced stdlib with better design.
| Einenlum wrote:
| This.
|
| We definitely need a better stdlib with appropriate data
| structures
| allan_s wrote:
| it's a chicken and the egg problem
|
| I think initiative like this drive a need for a more
| consistent, and even if slow, PHP has been
| deprecated/reworking its stdlib so I'm hopeful on this.
| foul wrote:
| >The stdlib is so inconsistent this will be a nightmare.
|
| I think that callables will end with being useless in this
| context and everyone will pipe closures to put that $x wherever
| the stdlib imposes.
| goykasi wrote:
| Is array_map and array_filter the common argument? One works
| against a single element, whereas the other works against
| multiple. What would you suggest a better param order? Do you
| know that array_walk exists?
| lordofgibbons wrote:
| Why doesn't PHP remove the horrid $ symbol for variables and the
| -> symbol for calling methods? I think those alone would do a lot
| more for its perception and adoption than adding the pipe
| operator.
| phatskat wrote:
| I actually don't mind them, and I've been out of daily PHP work
| for a few years now. When I see people denote internal
| variables with _ or elements with $ in JS, it rubs me the wrong
| way, but in PHP the $ is kind of nice.
|
| I also prefer the look of ->, it's _cool_
| kijin wrote:
| Other languages have all sorts of oversized arrows, like ==>
| and >>>.
|
| -> in PHP and C++ looks clean by comparison.
|
| I'll never forgive them for the brain fart they made of the
| namespace separator, though.
| LeonM wrote:
| > I'll never forgive them for the brain fart they made of
| the namespace separator, though.
|
| You mean the backslash? What's wrong with that?
| account42 wrote:
| To someone not already familiar with PHP it looks like
| you are trying to escape something.
| kijin wrote:
| The backslash is universally reserved as an escape
| character.
|
| It was decided almost 20 years ago so I'm totally used to
| it and there's no point arguing about it anymore. But the
| decision to reuse the backslash as a namespace separator
| still causes inconvenience from time to time. For
| example, when you write PSR-4 configuration in
| composer.json, all the backslashes need to be doubled,
| including (and especially!) the trailing backslash.
| esskay wrote:
| What would the alternative for a namespace separator be?
| The backslashes work well with PSR-4 to give a logical
| visual of the expected directory structure.
| account42 wrote:
| Since PHP takes a lot of syntax from C-like languages,
| the C++ namespace separator :: would be the obvious
| choice. Not sure if this conflicted with something in PHP
| though.
| jimktrains2 wrote:
| https://www.php.net/manual/en/language.oop5.paamayim-
| nekudot... scope resolution operator
| ahofmann wrote:
| I honestly don't understand this. The syntax is one of the most
| boring parts of a programming language. It is solved by the IDE
| (and now LLMs). I don't care about syntax, I care about what I
| can build. Since the beginning of time people argue about
| things like tabs vs. spaces, or the dollar sign and I honestly
| don't understand why that is. It just doesn't matter.
|
| Just to be clear: consistency does very much matter. The mental
| load of reading totally different styles of code is awful and a
| waste of energy.
| Timwi wrote:
| Readability (and hence, maintainability) is definitely a
| factor in decisions like this. In the particular case of the
| pipe operator though, the article does mention something it
| lets you do that you couldn't do before: in a context where
| only an expression is allowed (such as match), you can now do
| things that previously would not have worked because it would
| have required temporary variables.
| throw_m239339 wrote:
| > Why doesn't PHP remove the horrid $ symbol for variables and
| the -> symbol for calling methods? I think those alone would do
| a lot more for its perception and adoption than adding the pipe
| operator.
|
| Because it simply can't do that in a retro-compatible way. ->
| isn't so bad, C/C++ uses that as well. as for $ I guess it came
| from Perl. The point is already used for string concatenation,
| where other languages would overload the + operator.
| jeroenhd wrote:
| Same reason C doesn't introduce classes and C++ doesn't remove
| pointers: it's a) part of the core language and b) extremely
| inconsequential for any serious developer.
|
| I actually like the clarity these dollar signs add in a code
| base. Makes it easier to recognise (dynamic) functions, and
| makes it harder to accidentally shadow methods.
|
| Other languages will let you do `const Math = {}` and nuke the
| entire math library, or write stuff like `int fopen = 0;` to
| make the fopen method call inaccessible in that scope. With
| PHP, you don't need to restrict your variable name to
| "something that hopefully won't conflict with an obscure
| method".
|
| The -> is a leftover from an older programming language that
| I'd rather have replaced by a ., but not at the cost of
| breaking existing code (which it surely would).
| asddubs wrote:
| I do also appreciate that php has an explicit string concat
| operator rather than overloading +. Though of course it could
| just use another symbol for that to get rid of -> if we're
| talking about time travel. As it stands, you can't really do
| $obj.method(), because method() could be a function returning
| a string as well, so it's ambiguous
| account42 wrote:
| > The -> is a leftover from an older programming language
| that I'd rather have replaced by a ., but not at the cost of
| breaking existing code (which it surely would).
|
| Isn't it because . was already used for string concatenation
| in PHP. I mean the -> syntax wasn't invented by PHP but it
| didn't just inherit it without thought either.
| int_19h wrote:
| Indeed. And the reason why PHP used . for string
| concatenation is because Perl did.
| nolok wrote:
| Because back compat' is a very strong feature of the language,
| same reason "match" was created instead of replacing switch.
|
| As a result, taking a php 5.2 script and moving it up to 8.5 is
| super easy, and taking a PHP 4 one is barely harder only longer
| (since it probably uses the horrors that were register_globals
| and co).
|
| Ultimately, I prefer this than a fragmented ecosystem
| impossible to resolve.
| guskel wrote:
| The $ symbol gave me RSI as a dev just starting out. I will
| never forgive PHP for that. Such an unergonomic syntax.
| pkkm wrote:
| PHP has its significant flaws, but superficial syntactic
| differences aren't among them. In my experience, it takes two
| weeks to get used to pretty much any syntax.
| JaggerJo wrote:
| Thanks F#!
| ChocolateGod wrote:
| Why not just make types psuedo-objects?
|
| $myString.trim().replace("w", "h");
|
| Which has the advantage of also offering a clean alternative to
| the fragmented stdlib.
| reddalo wrote:
| I agree. But in PHP it would probably be like this:
|
| $myString->trim()->replace("w", "h");
| troupo wrote:
| Because pipes don't care about the type your function
| returns. And you don't need hundreds of methods on each type
| just in case. You just pipe the result of the previous
| function to the next one.
|
| And those functions can be business logic, or validation,
| or... Not just object methods
| williamdclt wrote:
| > Why not just make types psuedo-objects?
|
| With this sort of "just" I could build Paris out of matchsticks
| account42 wrote:
| Because duplicating the stdlib is probably not a good idea.
| habibur wrote:
| I tried to emulate something similar with PHP at one point. But
| the problem with PHP was parameter order. Especially in functions
| like array_key_exists() the array element is the 2nd parameter,
| while pipe operator expects the object to work on be the 1st
| parameter, the array in these cases.
|
| I believe they have solved this problem by now. Though no idea
| how.
| kijin wrote:
| The usual solution is to wrap it with a closure.
| function($x) { return array_key_exists('needle', $x); }
|
| Or using the arrow function syntax: fn($x) =>
| array_key_exists('needle', $x)
|
| The same trick also helps when you need to use functions with
| mandatory extra parameters, functions with pass-by-value
| parameters, etc.
| moebrowne wrote:
| If the Partial Function Application RFC passes then the
| closure wont be necessary
|
| https://wiki.php.net/rfc/partial_function_application_v2
| cess11 wrote:
| "A major limitation of the pipe operator is that all the
| callables in the chain must accept only one required parameter.
|
| For built-in functions, if the function does not accept any
| parameters, it cannot be used in a chain. For user-land PHP
| functions, passing a parameter to a function that does not accept
| any parameters does not cause an error, and it is silently
| ignored.
|
| With the pipe operator, the return value of the previous
| expression or the callable is always passed as the first
| parameter to the next callable. It is not possible to change the
| position of the parameter."
|
| https://php.watch/versions/8.5/pipe-operator
|
| In the light of these limitations I would not call the Elixir
| implementation "slightly fancier".
|
| I'm not so sure I'll be upgrading my local PHP version just for
| this but it's nice that they are adding it, I'm sure there is a
| lot of library code that would look much better if rewritten into
| this style.
| ossusermivami wrote:
| i wish python had something liek that to be honest
| kh_hk wrote:
| one can dream but i wouldn't keep high hopes. I feel functional
| patterns are left as second class citizens in python.
| abrookewood wrote:
| I love the pipe operator - one of the things I dig about Elixir
| though many languages have it. It's so much easier to reason
| about: $result = $arr |> fn($x) =>
| array_column($x, 'tags') |> fn($x) => array_merge(...$x)
| |> array_unique(...) |> array_values(...)
|
| VS array_values(array_unique(array_merge(...array_column($arr,
| 'tags'))));
| qwertox wrote:
| I don't see how this is hard to reason about, assuming this is
| the resulting code when using variables: $tags
| = ...array_column($arr, 'tags'); $merged_tags =
| array_merge($tags); $unique_tags =
| array_unique($merged_tags); $tag_values =
| array_values($unique_tags);
|
| It also makes it easier to inspect the values after each step.
| r34 wrote:
| Your version includes 4 variables. Pipes don't create those
| intermediate variables, so they are more memory efficient.
|
| Readability is mostly matter of habit. One reads easily what
| he/she is used to read.
| girvo wrote:
| > so they are more memory efficient
|
| They can be. It depends on the language, interpreter,
| compiler, and whether you do anything with those
| intermediate variables and the optimiser can get rid of
| them.
| r34 wrote:
| I thought we are talking about PHP8.5:)
| girvo wrote:
| Ah, I thought we were talking more generally about PL
| constructs that let you avoid intermediate variables,
| apologies :)
| qwertox wrote:
| It's true that pipes are more readable, and for many cases
| they will be the better option, but the example of nested
| functions just doesn't hold.
|
| That's like saying someone would use this:
| $result = $arr |> fn($x) => array_column($x, 'tags') |>
| fn($x) => array_merge(...$x) |> array_unique(...) |>
| array_values(...)
|
| which is harder to reason about than the nested functions.
| array_values( array_unique( array_merge(
| ...array_column($arr, 'tags') ) ) );
|
| or array_values( array_unique(
| array_merge( ...array_column($arr, 'tags')
| ) ) );
| navalino wrote:
| It is more readable and better option -- you have to
| parse it from the innermost function to the outermost
| just to understand what it's doing. With the pipe, it's
| more straightforward: you read it step by step -- do
| this, then that, then the next -- just like how you'd
| naturally read instructions.
| account42 wrote:
| The pipe syntax is much more readable than nested
| function calls when you need additional arguments for
| intermediate functions. With nested functions it becomes
| hard to see which functions those arguments belong to
| even if you try to help it with formatting.
| troupo wrote:
| Why didn't you format the pipes, too?
| $result = $arr |> fn($x) => array_column($x,
| 'tags') |> fn($x) => array_merge(...$x)
| |> array_unique(...) |> array_values(...)
|
| vs array_values( array_unique(
| array_merge( ...array_column($arr, 'tags')
| ) ) );
|
| With pipes you have linear sequence of data
| transformations. With nested function calls you have to
| start with innermost function and proceed all the way top
| the outermost layer.
| cess11 wrote:
| Such variable threading tends to be harder to skim through in
| production code, the intermediates become noise that's harder
| to filter out than a repeated symbol like |>.
|
| Preferably you should also be sure that the functions are
| compatible with the data type going in and only rarely have
| to break it to dump data mid-chain. If you expect that kind
| of erroring it's likely a builder-chain with -> is a better
| alternative and do logging in the methods.
| jeroenhd wrote:
| Correctly naming things is one of the harder challenges in
| computer programming. Putting effort into naming
| intermediates that you're going to throw out is a waste.
| Plus, the more variables you introduce, the more likely
| you'll accidentally re-use a variable name somewhere down the
| line.
|
| With PHP allowing variable initialization in one branch but
| not the other, and continuing execution by default when an
| undeclared variable is passed, declaring more variables can
| lead to an annoying class of bugs that would require
| significant (breaking) changes to the core language to
| completely eliminate.
| agos wrote:
| I don't think inspecting this is easier than adding |>
| IO.inspect() to a pipe chain
| harg wrote:
| Or putting `|> dbg()` at the end and let it print the value
| at every step of the chain
| wraptile wrote:
| modifying code just to attach a breakpoint is kinda silly
| in this day and age.
| lawn wrote:
| Introducing a new variable every single line adda a bunch of
| cognitive load compared to the pipe operator.
|
| It's much easier skim with the pipe operator and it's more
| robust too (for example reordering is a pain with variables,
| it's easy to introduce errors).
| kristopolous wrote:
| It's easier to write, copy paste, compose and comment
| const_cast wrote:
| The main problem with this approach, as someone who programs
| in PHP daily, is it pollutes the scope. That makes debugging
| much, much harder - you lose track of variables, and the
| current state of the program is complicated. IMO, if a value
| is a throwaway, like an intermediate, we shouldn't be able to
| use it. So method chaining or nesting function calls prevents
| them. Then, when we break after these functions, we can't see
| fake values. It also prevents someone in the future mutating
| the throwaway values. Someone could easily insert logic or a
| call that mutates something in the middle of this and breaks
| the chain.
|
| One way this is prevented in PHP is just using functions. But
| then you have functions just for the sake of scope, which
| isn't really what they're for. That introduces other
| annoyances.
| someothherguyy wrote:
| > It's so much easier to reason about
|
| Is it though? I don't think so.
| andrewflnr wrote:
| It tends to work a little better in Elixir, because you very
| rarely have to include one-off lambdas in your pipeline. The
| standard library functions are designed to work with the
| pipeline operator, where the thing you probably want to
| thread through is usually the first argument.
| DataDaemon wrote:
| This will be the year of PHP. People are tired of JS.
| beardyw wrote:
| I admire your conviction.
| Timwi wrote:
| I am indeed tired of JS; however, I'm not a fan of PHP either.
| I like the new pipe syntax as a concept, but when added to an
| already uncomfortable overall programming environment, it can
| only provide mild relief.
| rambambram wrote:
| You mean the fourth decade of PHP.
| oblio wrote:
| I'm not a fan of it, but JavaScript will outlive me.
| hajile wrote:
| I'd rather write JS/TS than most of the other popular
| languages.
| ioma8 wrote:
| The syntax is ugly as hell.
| frankzander wrote:
| Amen ... I mean PHP could have been such a good language if the
| syntax wouldn't be such a show stopper.
| JohnKemeny wrote:
| Thank you for your insight.
| someothherguyy wrote:
| composition would be much nicer than this, maybe soon
| moebrowne wrote:
| Might be sooner than you think. There are already RFCs for
| Partial Function Application and Function Composition:
|
| https://wiki.php.net/rfc/partial_function_application_v2
| https://wiki.php.net/rfc/function-composition
| mappu wrote:
| Every single one of those steps buffers into a temporary variable
| - this isn't efficient like a bash pipe.
| quietbritishjim wrote:
| Genuine question from a non-PHP user:
|
| Does PHP support iterator-like objects? Like Python I mean,
| where mydict.values() produces values on demand, not
| immediately realised as a list. Or are all steps necessarily
| guaranteed to be fully realised into a complete list?
| severak_cz wrote:
| yes, for a long time -
| https://www.php.net/manual/en/class.iterator.php
| quietbritishjim wrote:
| Interesting, but I suppose I was particularly interested if
| that's what's actually happening with the transformations
| in the example in the article. Are those making use of this
| protocol? The comment I originally replied to seems to
| imply they aren't.
| throw_m239339 wrote:
| PHP does have generators and iterators yes, although I
| personally rarely use them directly.
| Timwi wrote:
| The section where the article mentions function composition
| implies that it doesn't. The article says that compositing
| the functions before passing them into map would be an
| optimization. I take that to mean that without the
| composition, each map fully processes an array passed to it
| from the previous map, and the first map fully reads the
| whole file in the example. If it were iterable, the function
| composition would make no difference compared to a pipeline
| of multiple maps.
|
| Meanwhile, I'm confused as to why it sometimes says "map" and
| sometimes "array_map". The latter is what I'm familiar with
| and I know that it operates on a whole array with no lazy
| evaluation. If "map" isn't just a shorthand and actually
| creates a lazy-evaluated iterable, then I'm confused as to
| why the function composition would make any difference.
| troupo wrote:
| That's how function calls work in every language. Unless it's
| streams.
| dev_l1x_be wrote:
| Rust is next? Jokes aside, pipe operators in programming
| languages have a interesting side effect of enabling railway
| oriented programming that I miss the most when not working in F#.
| simonask wrote:
| The way function/trait resolution works in Rust, it's actually
| already quite idiomatic to code in this style (just using the
| dot operator). The standard library Iterator is a great example
| of this. :-)
|
| I don't think there's any significant push for an even terser
| syntax at the moment.
| realharo wrote:
| There is the `tap` crate (https://crates.io/crates/tap) which
| adds `tap`, `pipe` and their variants to everything.
| pknerd wrote:
| Am I the only one who found it ugly?
| defraudbah wrote:
| PHP is that weird beast that no one wants to praise and yet it
| works tremendously well for those who manage to tame it.
|
| I would likely never touch it as there are too many languages to
| use and what I know is more than enough to do my job, but I am
| super excited to see languages like PHP that aren't mainstream in
| my bubble to keep evolving
| nolok wrote:
| I'm not tempting you do to it or anything, but I want to say
| given your point of view, if one day you need a crude+ app and
| try to do it using laravel, you might be really surprised by
| what modern php actually is.
|
| There was a point were I thought the language and it ecosystem
| was going down the drain but then they recovered and modern php
| is 90% what do you want to do and don't worry about the how,
| it's easy.
|
| I don't use it much anymore, but every time I do all I see are
| possibilities.
| defraudbah wrote:
| what about deployment? I assume I need to scp files like
| Python or keep everything in a single giant PHP file? is that
| an option?
| nolok wrote:
| Deployment these days is essentially git pull && composer
| update
|
| Of course not if you use vm or serverless or whatever like
| this, but for a basic here is my crude app, that's what you
| do.
|
| Or if you want to go old school sure, just scp that
| directory, it still works like it did 30 years ago.
| defraudbah wrote:
| awesome, thank you
| claar wrote:
| Laravel Forge handles auto-deployment on push to master. Or
| if you want production zero downtime deployments, use
| Laravel Envoyer.
| cess11 wrote:
| I'll gladly praise it. It's a very practical language with good
| tooling and excellent amounts of libraries and scripts
| available. Performance is decent, development speed for tools,
| toys and prototypes is extreme.
|
| The standard library has a lot of good stuff for calling API:s,
| handling JSON, shelling out, string juggling and HTML
| publishing on a socket. In every typical install you also have
| common database interfaces. I've done so much problem solving
| at breathtaking speed in single file PHP scripts and PsySH over
| the years.
|
| The threading story isn't or wasn't very good so typically I've
| done logic in PHP and then driven it from something like a
| Scheme, Picolisp or Elixir when I've needed it.
| BiteCode_dev wrote:
| It's lovely to see how PHP keeps growing. It's far from what it
| was when I used to code with it in V3. I really thought it would
| be lost in its bad design, but the core devs kept at it, and it
| is, indeed, a pretty decent language now.
| cpursley wrote:
| Your move, JavaScript.
| mort96 wrote:
| I'm surprised that the example requires lambdas... What's the
| purpose of the `|> foo(...)' syntax if the function has to take
| exactly one operand? Why is it necessary to write this?
| $arr |> fn($x) => array_column($x, 'tags')
|
| Why doesn't this work? $arr |>
| array_column(..., 'tags')
|
| And when that doesn't work, why doesn't this work?
| $arr |> array_unique
| tossandthrow wrote:
| It is to interject the chained value at the right position in
| the function.
|
| They write that elixir has a slightly fancier version, it is
| likely around this, they mean (where elixir has first class
| support for arity > 1 functions)
| mort96 wrote:
| But the example suggests that it can't interject the chained
| value at the right position; if that was the case, the
| example would've been written as `|> array_column('tags',
| ...)`.
| wink wrote:
| yeah that sounds weird. defaulting to the first (or only)
| parameter would have made sense.
| ptx wrote:
| Apparently "foo(...)" is just the PHP syntax for a function
| reference, according to the "first-class callable" RFC [1]
| linked from the article.
|
| So where in Python you would say e.g. callbacks
| = [f, g]
|
| PHP requires the syntax $callbacks = [f(...),
| g(...)];
|
| As for the purpose of the feature as a whole, although it seems
| like it could be replaced with function composition as
| mentioned at the end of the article, and the function
| composition could be implemented with a utility function
| instead of dedicated syntax, the advantage of adding these
| operators is apparently [2] performance (fewer function calls)
| and facilitating static type-checking.
|
| [1] https://wiki.php.net/rfc/first_class_callable_syntax
|
| [2] https://wiki.php.net/rfc/function-
| composition#why_in_the_eng...
| mort96 wrote:
| Thanks, that makes sense!
| moebrowne wrote:
| There is a complementary RFC for partial function application
| which will allow calling a function with more than one
| parameter.
|
| https://wiki.php.net/rfc/partial_function_application_v2
| https://wiki.php.net/rfc/pipe-operator-v3#rejected_features
| zelphirkalt wrote:
| Hm. Looks like PHP actually got a modern feature there, and it is
| looking decent, not like the usual new PHP feature, that just
| looks worse than in other languages, where it has been standard.
| Consider me surprised, that they seem to have done a good job on
| this one. And they even dodged the bullet with making the right
| side callables, which avoids the trap of inventing new types of
| expressions and then not covering all cases.
| rogue7 wrote:
| This looks neat. However since I read about Koka's dot selection
| [0], I keep thinking that this is an even neater syntax:
|
| fun showit( s : string )
| s.encode(3).count.println
|
| However, this is of course impossible to implement in most
| languages as the dot is already meaningful for something else.
|
| [0] https://koka-lang.github.io/koka/doc/book.html#sec-dot
| throw-the-towel wrote:
| I think this is called uniform function call syntax.
| jprafael wrote:
| That syntax is very clean when it works. I think however the
| limitation of not being able to pipe arguments into 2nd, 3rd,
| ..., positions and keyword arguments, or variadic explosion
| like the syntax showcased in the article makes it less
| powerful.
|
| Are there other syntax helpers in that language to overcome
| this?
| account42 wrote:
| It still makes sense to have a clean syntax for the simple
| case. You can use currying (with or without first class
| language support) to handle more complex cases or just fall
| back to good old function composition or even loops.
| btbytes wrote:
| It is called Uniform [Function] Call Syntax.
|
| D has had this for decade(s):
| https://tour.dlang.org/tour/en/gems/uniform-function-call-sy...
|
| Nim too has it: https://nim-by-example.github.io/oop/
| ds_ wrote:
| One of the many joys of working with Clojure
| https://clojure.org/guides/threading_macros
| mhh__ wrote:
| Every language should have this.
|
| Forget about transforming existing code, it makes new code much
| more reasonable (the urge to come up with OOPslop is much weaker
| when functions are trivial) -- they're programming _languages_
| for a reason.
| xorcist wrote:
| "Essentially the same thing" as a shell pipe, except each
| function run sequentially in full, keeping output in a variable.
| So nothing like a shell pipe.
|
| For short constructions '$out = sort(fn($in)' is really easier to
| read. For longer you can break them up in multiple lines.
| $_ = fn_a($in) $_ = fb_b($_) $out = fn_c($_)
|
| Is it really "cognitive overhead" to have the temporary variable
| explicit? Being explicit can be a virtue. Readability matters in
| a programming language. If nothing else I think Python taught us
| that.
|
| I am skeptical to these types of sugar. Often what you really
| want is an iterator. The ability to hide that need carries clear
| risk.
| lihaciudaniel wrote:
| Wow dead language adds a special letter wowooeoowowoowoo
| dvtkrlbs wrote:
| PHP is a dead language since when ?
| zweifuss wrote:
| It's so dead that 79.2% of all websites rely on PHP to some
| degree.
|
| https://kinsta.com/php-market-share/
| sumeetdas wrote:
| The first typed programming language where I've seen pipe
| operator _| >_ in action was in F#. You can write something like:
| sum 1 2 |> multiply 3
|
| and it works because _| >_ pushes the output of the left
| expression as the last parameter into the right-hand function.
| _multiply_ has to be defined as: let multiply b c
| = b \* c
|
| so that _b_ becomes 3, and _c_ receives the result of _sum 1 2_.
|
| RHS can also be a lambda too: sum 1 2 |> (fun x
| -> multiply 3 x)
|
| _| >_ is not a syntactic sugar but is actually defined in the
| standard library as: let (|>) x f = f x
|
| For function composition, F# provides _> >_ (forward composition)
| and _< <_ (backward composition), defined respectively as:
| let (>>) f g x = g (f x) let (<<) f g x = f (g x)
|
| We can use them to build reusable composed functions:
| let add1 x = x + 1 let multiply2 x = x \* 2 let
| composed = add1 >> multiply2
|
| F# is a beautiful language. Sad that M$ stopped investing into
| this language long back and there's not much interest in (typed)
| functional programming languages in general.
| christophilus wrote:
| F# is excellent. It's tooling, ecosystem, and compile times are
| the reason I don't use it. I learned it alongside OCaml, and
| OCaml's compilation speed spoiled me.
|
| It is indeed a shame that F# never became a first class
| citizen.
| cosmos64 wrote:
| Lots of this, especially the tooling and ecosystem, improved
| considerably in the last couple of years.
|
| OCaml is a great language, as are others in the ML family.
| Isabelle is the first language that has introduced the |>
| pipe character, I think.
| dmead wrote:
| Haskell seems pretty dead as well. Good think php has another
| option for line noise though.
| gylterud wrote:
| What makes you believe Haskell is dead or even dying? New
| versions of GHC are coming out, and in my experience,
| developing Haskell has never been smoother (that's not to say
| it is completely smooth).
| epolanski wrote:
| And yet, while PHPs, Javas, and even nicher/newer languages
| like Kotlin, Clojure or Scala have plenty of killer
| software (software that makes it worth learning a language
| just to use that library/framework) Haskell has none after
| 30 years. Zero.
|
| Mind you, I know and like Haskell, but its issues are
| highly tied to the failure of the simple haskell initiative
| (also the dreadful state of its tooling).
| dmead wrote:
| yea I agree. haskell was my primary language for several
| years in the 00s. it's since had almost zero industry
| uptake. Don't come at me with jane street or the one off
| startup.
|
| I thought for a while I'd be able to focus on getting
| jobs that liked haskell. it never happened.
| chriswarbo wrote:
| I certainly wouldn't _focus_ on getting a Haskell job.
| Yet they _are_ out there; e.g. my current job is Haskell,
| and happens to be in the same sector (public transport)
| as my last job (which was mostly Scala).
|
| Also, I've found Haskell appropriate for some one-off
| tasks over the years, e.g.
|
| - Extracting a load of cross-referenced data from a huge
| XML file. I tried a few of our "common"
| languages/systems, but they all ran out of memory.
| Haskell let me quickly write something efficient-enough.
| Not sure if that's ever been used since (if so then it's
| definitely tech debt).
|
| - Testing a new system matched certain behaviours of the
| system it was replacing. This was a one-person task, and
| was thrown away once the old system was replaced; so no
| tech debt. In fact, this was at a PHP shop :)
| dmead wrote:
| Yea of course, its not really the focus for me either
| way. my point was that how great haskell seemed in grad
| school didn't match up with the real world interest.
|
| I use spark for most tasks like that now. Guido stole
| enough from haskell that pyspark is actually quite
| appealing for a lot of these tasks.
| instig007 wrote:
| > Guido stole enough from haskell that pyspark is
| actually quite appealing for a lot of these tasks.
|
| He didn't do his homework. Guido or whoever runs things
| around the python language committee nowadays didn't have
| enough mental capacity to realize that the `match` must
| be a variable bindable expression and never a statement
| to prevent type-diverging case branches. They also refuse
| to admit that a non-blocking descriptor on sockets has to
| be a default property of runtime and never assigned a
| language syntax for, despite even Java folks proving it
| by example.
| milutinovici wrote:
| It has PostgREST, which is the heart of supabase
| gylterud wrote:
| There are lots of great libraries, like repa, servant,
| megaparsec, gloss, yampa... as well as bindings to lots
| of standard stuff. I consider parsing to be one of
| Haskell's killer strengths and I would definitely use it
| to write a compiler.
|
| There is also some popular user facing software like
| Pandoc, written in Haskell. And companies using it
| internally.
| epolanski wrote:
| The only non irrelevant compiler ever written in Haskell
| is for another borderline dead project: Elm.
| gylterud wrote:
| What are you on about?
|
| The Agda compiler, Pugs, Cryptol, Idris, Copilot (not
| that copilot you are thinking of), GHC, PureScript,
| Elm...
|
| These might not be mainstream, but are (or were for Pugs,
| but the others are current) important within their niche.
| myko wrote:
| I will not stand for this Xmonad slander
| instig007 wrote:
| > also the dreadful state of its tooling
|
| this is plain and unsubstantiated FUD
|
| > Haskell has none after 30 years
|
| > I know Haskell
|
| I doubt it
| dmead wrote:
| it's easy to learn and speak latin as well.
| gylterud wrote:
| Yes, but there is very little modern latin slang. While
| GHC gives us great new extensions of Haskell quite often.
| 1-more wrote:
| Latin never paid my mortgage. Helped on the SATs though.
| munificent wrote:
| Compare the Redmonk rankings in 2020 to 2025:
|
| https://redmonk.com/sogrady/2020/02/28/language-
| rankings-1-2...
|
| https://redmonk.com/sogrady/2025/06/18/language-
| rankings-1-2...
|
| I think of languages as falling in roughly 3 popularity
| buckets:
|
| 1. A dominant conservative choice. These are ones you never
| have to justify to your CTO, the "no one ever got fired for
| buying IBM" languages. That's Java, Python, etc.
|
| 2. A well-known but deliberate choice. These are the
| languages where there is enough ecosystem and knowledge to
| be able to justify choosing them, but where doing so still
| feels like a deliberate engineering choice with some trade-
| offs and risk. Or languages where they are a dominant
| choice in one domain but less so in others. Ruby, Scala,
| Swift, Kotlin.
|
| 3. Everything else. These are the ones you'd have to fight
| to use professionally. They are either new and innovative
| or old and dying.
|
| In 2020, Haskell was close to Kotlin, Rust, and Dart. They
| were in the 3rd bucket but their vector pointed towards the
| second. In 2025, Kotlin and Dart have pulled ahead into the
| second bucket, but Haskell is moving in the other
| direction. It's behind Perl, and Perl itself is not exactly
| doing great.
|
| None of this is to say that Haskell is a bad language.
| There are many wonderful languages that aren't widely used.
| Popularity is hard and hinges on many extrinsic factors
| more than the merits of the language itself. Otherwise
| JavaScript wouldn't be at the top of the list.
| instig007 wrote:
| > In 2020, Haskell was close to Kotlin, Rust, and Dart.
| [...] In 2025, Kotlin and Dart have pulled ahead into the
| second bucket, but Haskell is moving in the other
| direction.
|
| > It's behind Perl, and Perl itself is not exactly doing
| great.
|
| Your comment reminded me of gamers who "play games" by
| watching "letsplay" videos on youtube.
| rpeden wrote:
| Is |> actually an operator in F#? I think it's just a regular
| function in the standard library but maybe I'm remembering
| incorrectly.
| laurentlb wrote:
| It's defined in the standard library and can be redefined by
| anyone.
|
| It's usually called operator because it uses an infix
| notation.
| int_19h wrote:
| All operators are functions in F#, e.g. this is valid: (+) 1
| 2
| tracker1 wrote:
| It's kind of wild that PHP gets a pipe(line) operator before JS
| finalizes its' version... of course they've had multiple
| competing proposals, of which I liked the F# inspired one the
| most... I've stopped relying on the likes of Babel (too much
| bloat) or I'd like to be able to use it. I used it for years
| for async functions and module syntax before they were
| implemented in node and browsers. Now it's hard to justify.
| Akronymus wrote:
| There are also ||> and |||> for automatically destructuring
| tuples and passing each part as a separate value along.
|
| And there are also the reverse pipes (<|, <|| and <|||)
|
| F# is, for me, the single most ergonomic language to work in.
| But yeah, M$ isn't investing in it, so there are very few
| oppurtunities to actually work with f# in the industry either.
| elric wrote:
| Makes for a fun programming paradigm, similar to Java's streams-
| with-lambdas. Great for readability. Not too fond of the |>
| operator though, requires 4 different keypresses on my keyboard
| layout, not terribly ergonomic. But I understand that options
| were limited and it is sort of clear.
| major505 wrote:
| So php now can do clojure like programing?
| donatj wrote:
| I had this argument in the PHP community when the feature was
| being discussed, but I think the syntax is much more complicated
| to _read_ , requiring backtracking to understand. It might be
| easier to write.
|
| Imagine you're just scanning code you're unfamiliar with trying
| to identify the symbols. Make sense of inputs and outputs, and
| you come to something as follows. $result =
| $arr |> fn($x) => array_column($x, 'values')
| |> fn($x) => array_merge(...$x) |> fn($x) =>
| array_reduce($x, fn($carry, $item) => $carry + $item, 0)
| |> fn($x) => str_repeat('x', $x);
|
| Look at this operation imaging your reading a big section of code
| you didn't write. This is embedded within hundreds or thousands
| of lines. Try to just make sense of what "result" is here? Do
| your eyes immediately shoot to its final line to get the return
| type?
|
| My initial desire is to know _what $result is_ generally
| speaking, before I decide if I want to dive into its derivation.
|
| It's a string. To find that out though, you have to skip all the
| way to the final line to understand what the type of $result is.
| When you're just making sense of code, it's far more about the
| destination than the path to get there, and understanding these
| require you to read them backwards.
|
| Call me old fashioned, I guess, but the self-documentating nature
| of a couple variables defining what things are or are doing seems
| important to writing maintainable code and lowering the
| maintainers' cognitive load. $values =
| array_merge(...array_column($arr, 'values')); $total =
| array_reduce($values, fn($carry, $item) => $carry + $item, 0);
| $result = str_repeat('x', $x);
| sandbags wrote:
| I don't disagree with your reasoning but I would have thought
| this pipe would be in an appropriately named function (at least
| that's how I'd use it in Elixir) to help understand the result.
| philjohn wrote:
| This is what a good IDE brings to the table, it'll show that
| $result is of type string.
|
| The pipe operator (including T_BLING) was one of the few things
| I enjoyed when writing Hack at Meta.
| xienze wrote:
| > This is what a good IDE brings to the table, it'll show
| that $result is of type string.
|
| I think the parent is referring to what the result _means_,
| rather than its type. Functional programming can, at times,
| obfuscate meaning a bit compared to good ol' imperative
| style.
| 8n4vidtmkvmk wrote:
| If you want meaning, don't call your variable "result"
| troupo wrote:
| > I think the syntax is much more complicated to read,
| requiring backtracking to understand.
|
| Same as with `array_merge(...array_column($arr, 'values'));` or
| similar nested function calls.
|
| > Imagine you're just scanning code you're unfamiliar with
| trying to identify the symbols. Make sense of inputs and
| outputs, and you come to something as follows.
|
| We don't have to imagine :) People working in languages
| supporting pipes look at similar code all day long.
|
| > but the self-documentating nature of a couple variables
| defining what things are or are doing seems important to
| writing maintainable code
|
| Pipes do not prevent you from using a couple of variables.
|
| In your example I need to keep track of $values variable, see
| where it's used, unwrap nested function calls etc.
|
| Or I can just look at the sequential function calls.
|
| What PHP should've done though is just pass the piped value as
| the first argument of any function. Then it would be much
| cleaner: $result = $arr |>
| array_column('values') |> array_merge() |>
| array_reduce(fn($carry, $item) => $carry + $item, 0) |>
| fn($x) => str_repeat('x', $x);
|
| I wouldn't be surprised if that's what will eventually happen
| WorldMaker wrote:
| The article addresses this pretty well.
|
| Quick summary: Hack used $$ (aka T_BLING) as the implicit
| parameter in a pipeline. That wasn't accepted as much fun as
| the name T_BLING can be. PHP looked for a solution and
| started looking for a Partial Function Application syntax
| they were happy with. That effort mostly deadlocked (though
| they hope to return to it) except for syntax
| some_function(...) for an unapplied function (naming a
| function without calling it).
|
| Seems like an interesting artifact of PHP functions not being
| first class objects. I wish them luck on trying to clean up
| their partial application story further.
| layer8 wrote:
| I completely agree about intermediate variables (and with
| explicit type annotations in a typed language) to make the code
| more intelligible.
|
| But maybe also, the pipe syntax would be better as:
| $arr |> fn($x) => array_column($x, 'values') |>
| fn($x) => array_merge(...$x) |> fn($x) =>
| array_reduce($x, fn($carry, $item) => $carry + $item, 0)
| |> fn($x) => str_repeat('x', $x) |= $result;
| drakythe wrote:
| I don't disagree with you. I had trouble reading the examples
| at first. But what immediately struck me is this syntax is
| pretty much identical to chaining object methods that return
| values. $result =
| $obj->query($sqlQuery)->fetchAll()[$key]
|
| so while the syntax is not my favorite, it at least maintains
| consistency between method chaining and now function chaining
| (by pipe).
| 8n4vidtmkvmk wrote:
| Speaking of query builders, we no longer have to guess
| whether it's mutating the underlying query object or cloning
| it with each operation. That's another big win for pipe IMO.
| epolanski wrote:
| You're conflating different concepts: familiarity and
| simplicity.
|
| I don't find the pipe alternative to be much harder to read,
| but I'd also favour the first one.
|
| In any case, we shouldn't judge software and it's features on
| familiarity.
| mcaruso wrote:
| People use method chaining all the time and don't have any
| issue with it? It's equivalent to something like:
| $result = $arr ->column('values')
| ->merge() ->reduce(fn($carry, $item) => $carry +
| $item, 0) ->repeat('x');
|
| I think this just comes down to familiarity.
| altairprime wrote:
| It reads well to me, as someone familiar with Perl map and jq
| lambda. But I would syntactic sugar it rather more strongly
| using a new `|=>` operator implying a distributive `|>` into
| its now-inferred-and-silent => arguments:
| $result = $arr |> fn($x) |=> array_column($x,
| 'values'), array_merge(...$x),
| array_reduce($x, fn($carry, $item) => $carry + $item, 0),
| str_repeat('x', $x);
|
| As teaching the parser to distribute `fn($x) |=> ELEM1, ELEM2`
| into `fn($x) => ELEM1 |> fn($x) => ELEM2 |> ...` so that the
| user isn't wasting time repeating it is exactly the sort of
| thing I love from Perl, and it's more plainly clear what it's
| doing -- and in what order, without having to unwrap parens --
| without interfering with any successive |> blocks that might
| have different needs.
|
| Of course, since I come from Perl, that lends itself well to
| cleaning up the array rollup in the middle using a reduce pipe,
| and then replacing all the words with operators to make
| incomprehensible gibberish but no longer needing to care about
| $x at all: $result = $arr |> $x:
| ||> 'values' |+< $i: $x + $i |>
| str_repeat('x', $x);
|
| Which rolls up nicely into a one-liner that is completely
| comprehensible if you know that | is column, + is merge, < is
| reduce, and have the : represent the syntactic sugar for
| conserving repetitions of fn($x) into $x using a stable syntax
| that the reduce can also take advantage of:
| $result = $arr |> $x: ||> 'values' |+< $i: $x + $i |>
| str_repeat('x', $x);
|
| Which reads as a nice simple sentence, since I grew up on Perl,
| that can be interpreted at a glance because it fits within a
| glance!
|
| So. I wouldn't necessarily implement _everything_ I can see
| possible here, because Perl proved that the space of people
| willing to parse symbols rather than words is not the complete
| programmer space. But I do stand by the helpfulness of the
| switch-like |= > as defined above =)
| hombre_fatal wrote:
| The problem with intermediate assignment is that they pollute
| your scope.
|
| You might have $values and then you transform it into $b,
| $values2, $foo, $whatever, and your code has to be eternally
| vigilant that it never accidentally refers to $values or any of
| the intermediate variables ever again since they only existed
| in service to produce some downstream result.
|
| Sometimes this is slightly better in languages that let you
| repeatedly shadow variables, `$values = xform1($values)`, but
| we can do better.
|
| That it's hard to name intermediate values is only a symptom of
| the problem where many intermediate values only exist as
| ephemeral immediate state.
|
| Pipeline style code is a nice general way to keep the top level
| clean.
| procaryote wrote:
| Put it in a function and the scope you pollute is only as big
| as you make it.
| hombre_fatal wrote:
| Functions also pollute the scope the same way. And you
| don't want to be forced to extract a function that is never
| reused just to hide intermediate values; you should only
| have to extract a function when you want the abstraction.
|
| The pipeline transformation specifically lets you clean
| this up with functions at the scope of each ephemeral
| intermediate value.
| skoskie wrote:
| That's why we have classes and namespaces.
|
| Anyone can write good or bad code. Avoiding new
| functionality and syntax won't change that.
| ckdot wrote:
| You definitely want to extract code into functions, even
| if you don't need to reuse it. Functions names are
| documentation. And you reduce the mental load from those
| who read the code.
| donatj wrote:
| If PHP scoped to blocks, it would be less of an issue, you
| could just wrap your procedural code in curly braces and call
| it a day { $foo = 'bar'; //
| only defined in this block }
|
| I use this reasonably often in Go, I wish it were a thing in
| PHP. PHP allows blocks like this but they seem to be noops
| best I can tell.
| tracker1 wrote:
| I think it's more a matter of what you're used to. It's simply
| an operator and syntax that you aren't used to seeing. Like if
| they added back a character into English that you aren't
| familiar with and started using it in words that you no longer
| recognize.
|
| A lot of people could say the same of the rest/spread syntax as
| well.
| int_19h wrote:
| It's no different than chained property accesses or method
| calls, or more generally nested expressions. Which is to say,
| if you overuse it, you hamper readability, but if you have a
| named result for every single operation, it is also hard to
| read because it introduces too much noise.
| penguin_booze wrote:
| I don't imagine it's widely known (which I completely
| understand): vimscript has an arrow operator with similar piping
| effect, a la foo->bar(baz)->qux() . See the doc:
| https://vimhelp.org/eval.txt.html#method.
| mg wrote:
| I'm confused about the rationale behind: |>
| fn($x) => array_column($x, 'tags')
|
| Why is that inlined function necessary? Why not just
| |> array_column(..., 'tags')
|
| ?
|
| I mean, I understand that it is because the way this operator was
| designed. But why?
| rafark wrote:
| > |> array_column(..., 'tags')
|
| This syntax is invalid. But it will be possible next year with
| the proposed partial function application rfc
|
| array_column(?, 'tags')
|
| https://wiki.php.net/rfc/partial_function_application_v2
| LorenDB wrote:
| Reminds me of D's Uniform Function Call Syntax[0], which allows
| you to rewrite bar(foo(sort(myArray))) as
| myArray.sort().foo().bar(). The difference is that D allows extra
| function arguments, keeping the passed-in value as the first
| argument. So you could have myArray.sort().writeln("extra text"),
| for example.
|
| [0]: https://tour.dlang.org/tour/en/gems/uniform-function-call-
| sy...
| jcmontx wrote:
| Very nice, great F# feature, hope to see it in many other
| languages!
| chuck8088 wrote:
| This article makes a great case WHY the pipe operator is useful,
| but why didn't they just rewrite those functions to support
| method chaining? ` $profit = [1, 4, 5] .loadSeveral()
| .filter(isOnSale()) .map(sellWidget()) .array_sum(); ` this has
| the side benefit of 'looking normal'
| wsatb wrote:
| Backwards compatibility. The language has done a pretty amazing
| job at adding features over the last 10 years without really
| breaking a lot of old code. I believe PHP still runs about 75%
| of the internet, so that's pretty huge.
| moebrowne wrote:
| The Python 2 to 3 upgrade is a example of how important
| backwards compatibility is
| troupo wrote:
| Because pipes work on all functions, not just object methods.
| So your business logic, validations etc. don't have to be
| methods of the built-in objects.
|
| And there's nothing abnormal about pipes
| Mystery-Machine wrote:
| PHP: $result = $arr |>
| fn($x) => array_column($x, 'tags') // Gets an array of arrays
| |> fn($x) => array_merge(...$x) // Flatten into one big
| array |> array_unique(...) //
| Remove duplicates |> array_values(...)
| // Reindex the array. ; // <- wtf
|
| Ruby: result = arr.uniq.flatten.map(&:tags)
|
| I understand this is not pipe operator, but just look at that
| character difference across these two languages.
|
| // <- wtf
|
| This comment was my $0.02.
| Alifatisk wrote:
| Can't the pipe operator be easily mimicked in Ruby thanks to
| its flexibility?
|
| I'm thinking of something like this: class
| Object def |>(fn) fn.call(self)
| end end
|
| which then can be in the following way:
| result = arr |> ->(a) { a.uniq } |> ->(a) {
| a.flatten } |> ->(a) { a.map(&:tags) }
|
| Or if we just created an alias for then #then method:
| class Object alias_method :|>, :then end
|
| then it can be used like in this way: arr
| |> :uniq.to_proc |> :flatten.to_proc |>
| ->(a) { a.map(&:tags) }
| rafark wrote:
| The great thing about this pipe operator is that it accepts
| any callable expression. I'm writing a library to make these
| array and string functions more expressive.
|
| For example, in php 8.5 you'll be able to do:
|
| [1,1,2,3,2] |> unique
|
| And then define "unique" as a constant with a callback
| assigned to it, roughly like:
|
| const unique = static fn(array $array) : array =>
| array_unique($array);
|
| Much better.
| moebrowne wrote:
| The trailing semi-colon on a new line helps prevent Git
| conflicts and gives cleaner diffs.
|
| It's the same reason PHP allows trailing commas in all lists.
| daneel_w wrote:
| Putting the delimiter on a line of its own is a syntactical
| trick that helps bringing small additions down to a neater
| 1-line diff instead of a 2-line diff. You've probably run into
| it many times before in other contexts without thinking of it.
| Arrays/hashes, quoted multi-line strings etc.
| jadbox wrote:
| Now if only JS or Typescript can jump on this ship!
| scop wrote:
| This is great! Hat tip to PHP. I first came across pipes in
| Elixir and have ever since missed it in every other language. Two
| observations:
|
| - pipes make you realize how much song and dance you do for
| something quite simple. Nesting, interstitial variables, etc all
| obscuring what is in effect and very orderly set of operations.
|
| - pipes really do have to be a first class operator of the
| language. I've tried using some pipe-like syntactic sugar in
| languages without pipes and while it does the job, a lot of
| elegance and simplicity is lost. It feels like you are using a
| roundabout thing and thus, in the end, doesn't really achieve the
| same level of simplicity. Things can get very deranged if you are
| using a language in a way it wasn't designed for and even though
| I love pipes I've seen "fake pipes" make things more complicated
| in languages without them.
| elif wrote:
| I used php professionally for a decade and I still don't get why
| in the year 2025 we need to reinvent syntax that is virtually
| standard in every language
| dagi3d wrote:
| I wish they reconsider it again i ruby
| adius wrote:
| PHP getting a pipe operator with ways to implement a Maybe Monad
| was definitely not on my 2025 bingo card.
|
| But any changes making mainstream languages more functional are
| highly welcome! It's just more ergonomic than imperative code.
| flufluflufluffy wrote:
| It's cool. Personally I probably won't be using it though. I
| think a few temp variables or dedicated functions to do some
| computation that takes more than 2 or 3 iterated operations is
| better for readability and maintainability.
___________________________________________________________________
(page generated 2025-08-05 23:01 UTC)