Hi Rowan
On Thu, Apr 3, 2025 at 1:59 PM Rowan Tommins [IMSoP]
<[email protected]> 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