Readit News logoReadit News
sumeetdas · 22 days ago
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 · 22 days ago
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 · 22 days ago
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.

Akronymus · 21 days ago
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.

tracker1 · 21 days ago
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.
rpeden · 22 days ago
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 · 21 days ago
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 · 21 days ago
All operators are functions in F#, e.g. this is valid: (+) 1 2
dmead · 22 days ago
Haskell seems pretty dead as well. Good think php has another option for line noise though.
gylterud · 22 days ago
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).
bapak · 22 days ago
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...
avaq · 22 days ago
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 · 22 days ago
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?
xixixao · 22 days ago
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 · 21 days ago
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.

wouldbecouldbe · 22 days ago
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.
chilmers · 22 days ago
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.

bapak · 22 days ago
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.

troupo · 22 days ago
> 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

hajile · 21 days ago
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.

Martinussen · 22 days ago
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.
purerandomness · 22 days ago
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.
EGreg · 22 days ago
It’s not really chaining

More like thenables / promises

te_chris · 22 days ago
Dots call functions on objects, pipe passes arguments to functions. Totally missing the point.
fergie · 22 days ago
Good- the [real world examples of pipes in js](https://github.com/tc39/proposal-pipeline-operator?tab=readm...) are deeply underwhelming IMO.
epolanski · 22 days ago
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.

lacasito25 · 22 days ago
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.

77pt77 · 22 days ago
Best you'll git will be 3 new build systems and 10 new frameworks
defraudbah · 22 days ago
do not let me start on monads in golang...

both are going somewhere and super popular though

donatj · 22 days ago
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);

hombre_fatal · 21 days ago
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.

donatj · 21 days ago
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.

procaryote · 21 days ago
Put it in a function and the scope you pollute is only as big as you make it.
drakythe · 22 days ago
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 · 21 days ago
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.
altairprime · 21 days ago
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 =)

philjohn · 22 days ago
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 · 22 days ago
> 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.

epolanski · 22 days ago
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.

sandbags · 22 days ago
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.
troupo · 22 days ago
> 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 · 21 days ago
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.

mcaruso · 22 days ago
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.

tracker1 · 21 days ago
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.

layer8 · 22 days ago
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;

int_19h · 21 days ago
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.
sandreas · 22 days ago
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...

hn8726 · 22 days ago
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.

troupo · 22 days ago
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 · 22 days ago
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 :-)

cess11 · 22 days ago
PHP has traits, just invent that API, put it in a trait and add it to your data classes.
sandreas · 22 days ago
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.

stefanfisk · 22 days ago
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.
Contortion · 22 days ago
Basically what Collections do in Laravel.
sandreas · 22 days ago
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.

abrookewood · 22 days ago
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 · 22 days ago
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.

jeroenhd · 22 days ago
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.

lawn · 22 days ago
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).

agos · 22 days ago
I don't think inspecting this is easier than adding |> IO.inspect() to a pipe chain
const_cast · 21 days ago
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.

cess11 · 22 days ago
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.

kristopolous · 22 days ago
It's easier to write, copy paste, compose and comment
r34 · 22 days ago
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.

someothherguyy · 22 days ago
> It's so much easier to reason about

Is it though? I don't think so.

andrewflnr · 22 days ago
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.
mort96 · 22 days ago
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

ptx · 22 days ago
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 · 22 days ago
Thanks, that makes sense!
moebrowne · 22 days ago
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_v2https://wiki.php.net/rfc/pipe-operator-v3#rejected_features

Deleted Comment

tossandthrow · 22 days ago
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 · 22 days ago
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', ...)`.
gbalduzzi · 22 days ago
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

account42 · 22 days ago
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.

noduerme · 22 days ago
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?

cess11 · 22 days ago
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.

Einenlum · 22 days ago
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
colecut · 22 days ago
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 · 22 days ago
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

Y-bar · 22 days ago
> 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"?

account42 · 22 days ago
So they are consistent because they are consistently inconsistent??

There isn't a good reason for PHP to have inherited C's issues here.

gbalduzzi · 22 days ago
`strlen`, `strncmp` and `strtolower` but `str_split` and `str_contains`.

How it that consistent?

cess11 · 22 days ago
"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.