Hi Rowan

On Thu, Apr 3, 2025 at 1:59 PM Rowan Tommins [IMSoP]
<imsop....@rwec.co.uk> wrote:
>
> At first, I thought Ilija's example looked pretty neat, but having
> thought about it a bit more, I think the "first-arg" approach makes a
> handful of cases nicer at the cost of a lot of magic, and making other
> cases worse.

I think "handful" is the word to focus on. As noted, I believe the
primary use-case for pipes are iterators. If that's true, then an
implicit first-arg approach should cover the majority of examples,
while complicating the rest. Whether that's a worthwhile trade-off is
for the community to decide.

To me, pipes improve readability when they behave like methods, i.e.
they perform some operation on a subject. This resembles Swift's
protocol extensions or Rust's trait default implementations, except
using a different "method" call operator. With this mental model, the
first-arg approach seems intuitive to me. Once parameters are out of
order, the pipe examples with partial function application cause more
cognitive overhead for me, but this is entirely subjective.

> If we have a special case where the right-hand side *is* an expression,
> evaluated as a single-argument callable/Closure, that's even more scope
> for confusion. [cf my thoughts in the async thread about keeping the
> right-hand side of "spawn" consistent]

To clarify: I'm not in favor of this syntax either. While I originally
mentioned it as a possibility, I later noted that `lhs |> {rhs}` would
be less ambiguous, given that {} is not legal in the general
expression context, while also resembling the `lhs->{rhs}` syntax to a
degree. However, because {} is not simpler than `lhs |> rhs()`, I
mentioned neither in my e-mail.

> The cases it makes nicer are where you are chaining existing functions
> with the placeholder as first (but not only) parameter.

If we decide not to add an iterator API that works well with
first-arg, then I agree that this is not the right approach. But if we
do, then neither of your examples are problematic.

> // first-arg chaining
> $someChain |> fn($string) => explode(':', $string)();

As for string functions, I had a quick look through the stubs and
could only find a handful of functions that are not already
subject-first:

* preg_*/mb_ereg*
* mb_split
* explode

Maybe my search was flawed, let me know if there are any that I
missed. explode() specifically usually appears first in a chain (or
deepest in nested calls), which means it could just remain a normal
function call.

$result = explode(' ', $str) |> filter(...) |> map(...) |> join(' ');

The iterator API would improve the array_filter() example. Admittedly,
you might not always want to use iterators. A single array_map() would
likely be faster than going through the iterator API. But then again,
single calls aren't chains, so they won't benefit much from pipes to
begin with.

Ilija

Reply via email to