"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.
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.
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.
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?
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);
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.
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)
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.
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.
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.
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.
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?
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.
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)
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', ...)`.
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.
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).
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.
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.
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.
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.
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.
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
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.
> 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).
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.
> 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”.
> 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"?
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?
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.
;
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
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
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.
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.
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.
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?
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.
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?
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.
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.
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.
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.
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#.
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.
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
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.
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.
"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."
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.
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.
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.
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).
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
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.
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.
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.
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.
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.
> 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.
"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.
Is it really "cognitive overhead" to have the temporary variable explicit? Being explicit can be a virtue.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.
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...
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...
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?
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
and especially when you end up writing code like when using existing language features.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.
[delayed]
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)
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.
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.
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.
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.
K&R C was good enough for UNIX System V, why bother with C89.
K&R C was the apex, we've just been going downhill since.
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.
> 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
It’s not really chaining
More like thenables / promises
It looks like chaining, but with possibility of adding custom functions?
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
It requires parentheses `(3).myMethod()` but you can by monkey patching the Number prototype. Very bad idea, but you absolutely can.
Dots call functions on objects, pipe passes arguments to functions. Totally missing the point.
Good- the [real world examples of pipes in js](https://github.com/tc39/proposal-pipeline-operator?tab=readm...) are deeply underwhelming IMO.
do not let me start on monads in golang...
both are going somewhere and super popular though
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.
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?
Why doesn't this work? And when that doesn't work, why doesn't this work?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.
PHP requires the syntax 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...
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)
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', ...)`.
yeah that sounds weird. defaulting to the first (or only) parameter would have made sense.
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:
VS array_values(array_unique(array_merge(...array_column($arr, 'tags'))));I don't see how this is hard to reason about, assuming this is the resulting code when using variables:
It also makes it easier to inspect the values after each step.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.
I don't think inspecting this is easier than adding |> IO.inspect() to a pipe chain
Or putting `|> dbg()` at the end and let it print the value at every step of the chain
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).
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.
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.
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:
which is harder to reason about than the nested functions. orThe 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.
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.
> 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.
I thought we are talking about PHP8.5:)
> It's so much easier to reason about
Is it though? I don't think so.
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.
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
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:
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
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
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.
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"
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.
> 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).
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.
> 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.
> 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"?
So they are consistent because they are consistently inconsistent??
There isn't a good reason for PHP to have inherited C's issues here.
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
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?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
You can do
to capture stuff from the local environment.Edit: And you can pass by reference:
Never done it in practice, though, not sure if there are any footguns besides the obvious hazards in remote mutation.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#).
feels much more complex than writing having array extension methods for column, flatten, unique and values.1: https://kotlinlang.org/docs/extensions.html#extension-functi...
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.
PHP has traits, just invent that API, put it in a trait and add it to your data classes.
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
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
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.
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?
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.
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.
>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.
This.
We definitely need a better stdlib with appropriate data structures
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.
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 )
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
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?
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.
I think this is called uniform function call syntax.
Every single one of those steps buffers into a temporary variable - this isn't efficient like a bash pipe.
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?
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.
yes, for a long time - https://www.php.net/manual/en/class.iterator.php
PHP does have generators and iterators yes, although I personally rarely use them directly.
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.
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.
raku has had feed operators like this since its inception
uses ==> and <== for leftwardtrue 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
One of the many joys of working with Clojure https://clojure.org/guides/threading_macros
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.
The usual solution is to wrap it with a closure.
Or using the arrow function syntax: The same trick also helps when you need to use functions with mandatory extra parameters, functions with pass-by-value parameters, etc.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#.
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.
Am I the only one who found it ugly?
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.
Because duplicating the stdlib is probably not a good idea.
> Why not just make types psuedo-objects?
With this sort of "just" I could build Paris out of matchsticks
I agree. But in PHP it would probably be like this:
$myString->trim()->replace("w", "h");
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
composition would be much nicer than this, maybe soon
Your move, JavaScript.
This will be the year of PHP. People are tired of JS.
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.
You mean the fourth decade of PHP.
I admire your conviction.
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.
"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.
Thanks F#!
i wish python had something liek that to be honest
one can dream but i wouldn't keep high hopes. I feel functional patterns are left as second class citizens in python.
C'mon Dart! Follow up please. Go is a lost cause...
I feel like a kindergartener writing go. I wish another language got popular in the space go is used for.
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.
The syntax is ugly as hell.
Thank you for your insight.
Amen ... I mean PHP could have been such a good language if the syntax wouldn't be such a show stopper.
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.
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).
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
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.
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_
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.
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.
> 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?
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.
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.
> 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.