Hi,
I tried to read on why the pipe RFC failed but by and large I also failed.
The discussion on https://github.com/php/php-src/pull/7214 is very short.
https://externals.io/message/114770 is not so short but it seems not to
cover the latest version which uses first class functions?
Could someone please give me a summary why it failed? I really would like
to see it succeed :) I am writing code if not daily but certainly weekly
that certainly looks like a pipeline.
Of course, if the preference is to bring nikic/scalar_objects into PHP
core, that'd be even better :) but I am aware of
https://github.com/nikic/scalar_objects/issues/20#issuecomment-569520181
so that's perhaps not going to happen any time soon.
Thanks
- ChX
2023-07-17 14:25 GMT+02:00, Karoly Negyesi karoly@negyesi.net:
Hi,
I tried to read on why the pipe RFC failed but by and large I also failed.
The discussion on https://github.com/php/php-src/pull/7214 is very short.
https://externals.io/message/114770 is not so short but it seems not to
cover the latest version which uses first class functions?Could someone please give me a summary why it failed? I really would like
to see it succeed :) I am writing code if not daily but certainly weekly
that certainly looks like a pipeline.
The pipe RFC was kinda forced in before a deadline, no?
My own two cents:
- It's trivial to implement a pipe() function or a Pipe class
- A Pipe class is better than both a function and built-in operator,
since it can be configured with custom behaviour, e.g. stop or throw
on empty payload, or repeat on a collection, or even with parallelism
or concurrency - If I had voting rights, I'd vote in favor in a pipe operator :)
Olle
2023-07-17 14:25 GMT+02:00, Karoly Negyesi karoly@negyesi.net:
Hi,
I tried to read on why the pipe RFC failed but by and large I also failed.
The discussion on https://github.com/php/php-src/pull/7214 is very short.
https://externals.io/message/114770 is not so short but it seems not to
cover the latest version which uses first class functions?Could someone please give me a summary why it failed? I really would like
to see it succeed :) I am writing code if not daily but certainly weekly
that certainly looks like a pipeline.The pipe RFC was kinda forced in before a deadline, no?
My own two cents:
- It's trivial to implement a pipe() function or a Pipe class
- A Pipe class is better than both a function and built-in operator,
since it can be configured with custom behaviour, e.g. stop or throw
on empty payload, or repeat on a collection, or even with parallelism
or concurrency- If I had voting rights, I'd vote in favor in a pipe operator :)
From my recollection, there were a couple of things involved.
- It was intended to pair with the PFA RFC, which didn't pass, which made it a bit less compelling.
- It was close to the RFC deadline, and it seems people get squeamish around that.
- Some folks wanted Hack-style pipes instead of the pipes used by every other language with pipes. I've written before on why that's a worse design.
- Arguments that it can be done in user space, which is not true, as I have a user-space implementation and it's comparatively cumbersome and definitely slower than a native operator would be.
- General "meh" attitude on FP features in general from some people.
Side note to Olle: If you want a customizable pipe, you've just described a Monad. :-) It's literally "contextually-sensitive func concatenation." A monadic bind operator would be harder to do with PHP's weaker type system, but there are ways it could be done.
I'd like to bring the Pipes RFC back if I thought there was a reasonable potential for it to pass this time. I would use the crap out of it myself. Though I've also since decided that we do need a straight up func concat operator as well. (I previously thought one would be sufficient, but in practice I think we do want both.)
--Larry Garfield
2023-07-17 18:58 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-17 14:25 GMT+02:00, Karoly Negyesi karoly@negyesi.net:
Hi,
I tried to read on why the pipe RFC failed but by and large I also
failed.The discussion on https://github.com/php/php-src/pull/7214 is very
short.https://externals.io/message/114770 is not so short but it seems not to
cover the latest version which uses first class functions?Could someone please give me a summary why it failed? I really would
like
to see it succeed :) I am writing code if not daily but certainly weekly
that certainly looks like a pipeline.The pipe RFC was kinda forced in before a deadline, no?
My own two cents:
- It's trivial to implement a pipe() function or a Pipe class
- A Pipe class is better than both a function and built-in operator,
since it can be configured with custom behaviour, e.g. stop or throw
on empty payload, or repeat on a collection, or even with parallelism
or concurrency- If I had voting rights, I'd vote in favor in a pipe operator :)
From my recollection, there were a couple of things involved.
- It was intended to pair with the PFA RFC, which didn't pass, which made
it a bit less compelling.- It was close to the RFC deadline, and it seems people get squeamish
around that.- Some folks wanted Hack-style pipes instead of the pipes used by every
other language with pipes. I've written before on why that's a worse
design.- Arguments that it can be done in user space, which is not true, as I have
a user-space implementation and it's comparatively cumbersome and definitely
slower than a native operator would be.- General "meh" attitude on FP features in general from some people.
Side note to Olle: If you want a customizable pipe, you've just described a
Monad. :-) It's literally "contextually-sensitive func concatenation." A
monadic bind operator would be harder to do with PHP's weaker type system,
but there are ways it could be done.
Mm I don't really agree with that, I think monads make sense only in
languages which support them syntactically. A Pipe class is a very
straight-forward construction, and the blog posts I've read about
monads in PHP don't look pretty at all; lots of syntactic noise going
on. But that's another discussion... :)
Olle
2023-07-17 18:58 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-17 14:25 GMT+02:00, Karoly Negyesi karoly@negyesi.net:
Hi,
I tried to read on why the pipe RFC failed but by and large I also
failed.The discussion on https://github.com/php/php-src/pull/7214 is very
short.https://externals.io/message/114770 is not so short but it seems not to
cover the latest version which uses first class functions?Could someone please give me a summary why it failed? I really would
like
to see it succeed :) I am writing code if not daily but certainly weekly
that certainly looks like a pipeline.The pipe RFC was kinda forced in before a deadline, no?
My own two cents:
- It's trivial to implement a pipe() function or a Pipe class
- A Pipe class is better than both a function and built-in operator,
since it can be configured with custom behaviour, e.g. stop or throw
on empty payload, or repeat on a collection, or even with parallelism
or concurrency- If I had voting rights, I'd vote in favor in a pipe operator :)
From my recollection, there were a couple of things involved.
- It was intended to pair with the PFA RFC, which didn't pass, which made
it a bit less compelling.- It was close to the RFC deadline, and it seems people get squeamish
around that.- Some folks wanted Hack-style pipes instead of the pipes used by every
other language with pipes. I've written before on why that's a worse
design.- Arguments that it can be done in user space, which is not true, as I have
a user-space implementation and it's comparatively cumbersome and definitely
slower than a native operator would be.- General "meh" attitude on FP features in general from some people.
Side note to Olle: If you want a customizable pipe, you've just described a
Monad. :-) It's literally "contextually-sensitive func concatenation." A
monadic bind operator would be harder to do with PHP's weaker type system,
but there are ways it could be done.Mm I don't really agree with that, I think monads make sense only in
languages which support them syntactically. A Pipe class is a very
straight-forward construction, and the blog posts I've read about
monads in PHP don't look pretty at all; lots of syntactic noise going
on. But that's another discussion... :)
At its most basic:
class Foo {
public function __construct(public readonly mixed $val) {}
public __bind(callable $c) {
return $c($this->val);
}
}
new Foo('beep') >>= func(...);
Where >>= is the operator that translates to "Call __bind() on the LHS object with the callable on the RHS." Poof, we now have a monad operator. This one is effectively the same as |> as it doesn't do anything, but you can do whatever you want in the __bind() method.
But that's a separate operation from |> or func concat. (Unless we wanted to implement it as an operator override for |> that an object can do, which... is an option. I don't know if I like that option, but is an option.)
The implementation for all of this is fairly trivial. It's agreeing to do it that is the challenge.
--Larry Garfield
2023-07-17 21:57 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-17 18:58 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-17 14:25 GMT+02:00, Karoly Negyesi karoly@negyesi.net:
Hi,
I tried to read on why the pipe RFC failed but by and large I also
failed.The discussion on https://github.com/php/php-src/pull/7214 is very
short.https://externals.io/message/114770 is not so short but it seems not
to
cover the latest version which uses first class functions?Could someone please give me a summary why it failed? I really would
like
to see it succeed :) I am writing code if not daily but certainly
weekly
that certainly looks like a pipeline.The pipe RFC was kinda forced in before a deadline, no?
My own two cents:
- It's trivial to implement a pipe() function or a Pipe class
- A Pipe class is better than both a function and built-in operator,
since it can be configured with custom behaviour, e.g. stop or throw
on empty payload, or repeat on a collection, or even with parallelism
or concurrency- If I had voting rights, I'd vote in favor in a pipe operator :)
From my recollection, there were a couple of things involved.
- It was intended to pair with the PFA RFC, which didn't pass, which
made
it a bit less compelling.- It was close to the RFC deadline, and it seems people get squeamish
around that.- Some folks wanted Hack-style pipes instead of the pipes used by every
other language with pipes. I've written before on why that's a worse
design.- Arguments that it can be done in user space, which is not true, as I
have
a user-space implementation and it's comparatively cumbersome and
definitely
slower than a native operator would be.- General "meh" attitude on FP features in general from some people.
Side note to Olle: If you want a customizable pipe, you've just described
a
Monad. :-) It's literally "contextually-sensitive func concatenation."
A
monadic bind operator would be harder to do with PHP's weaker type
system,
but there are ways it could be done.Mm I don't really agree with that, I think monads make sense only in
languages which support them syntactically. A Pipe class is a very
straight-forward construction, and the blog posts I've read about
monads in PHP don't look pretty at all; lots of syntactic noise going
on. But that's another discussion... :)At its most basic:
class Foo {
public function __construct(public readonly mixed $val) {}public __bind(callable $c) {
return $c($this->val);
}
}new Foo('beep') >>= func(...);
Where >>= is the operator that translates to "Call __bind() on the LHS
object with the callable on the RHS." Poof, we now have a monad operator.
This one is effectively the same as |> as it doesn't do anything, but you
can do whatever you want in the __bind() method.
-
You also want syntactic sugar around this to "flatten" monadic
usage; in OCaml it'slet*
. -
This way you won't get read-process-write pipelines as first-class
values, which you can do if you just make a Pipe class instead. I
think. Correct me if I'm wrong. :)
Olle
So. Let's get back to pipelines.
I wrote this email because I was manipulating a robots meta tag string:
$robots_array = explode(', ', $robots_string);
$robots_array = array_diff($robots_array, $remove);
$robots_string = implode(', ', $robots_array);
Very pipeline-ish.
You could write a userspace pipeline
https://gist.github.com/chx/6638aba76d8b414ffedc7e5af78fb479 but this has
the advantage of being spectacularly ugly and slow as well.
Just converting it into a pipeline with
$robots_string
|> fn ($x) => explode(', ', $x)
|> fn ($x) => array_diff($x, $remove)
|> fn ($x) => implode(', ', $x)
has the same characteristics. What I would love to see:
$robots_string
|> explode(', ', $)
|> array_diff($, $remove)
|> implode(', ', $)
While that does look like partial function application, it does not need to
be. It could be just syntactic sugar. Namely, in the expression following
|> a $ is replaced by the entire expression before the |>. Step by step:
explode(', ', $robots_string)
|> array_diff($, $remove)
|> implode(', ', $)
array_diff(explode(', ', $robots_string), $remove)
|> implode(', ', $)
implode(', ', array_diff(explode(', ', $robots_string), $remove))
We could use any other symbol but I liked the $ sign here because the
entire point is not needing a temporary variable between steps so the $
sign stands for "unnamed temporary variable". I also believe it can't lead
to any ambiguous situation (famous last words).
What do you think of this simple version?
On Tue, Jul 18, 2023 at 2:57 PM Olle Härstedt olleharstedt@gmail.com
wrote:
2023-07-17 21:57 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-17 18:58 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-17 14:25 GMT+02:00, Karoly Negyesi karoly@negyesi.net:
Hi,
I tried to read on why the pipe RFC failed but by and large I also
failed.The discussion on https://github.com/php/php-src/pull/7214 is very
short.https://externals.io/message/114770 is not so short but it seems not
to
cover the latest version which uses first class functions?Could someone please give me a summary why it failed? I really would
like
to see it succeed :) I am writing code if not daily but certainly
weekly
that certainly looks like a pipeline.The pipe RFC was kinda forced in before a deadline, no?
My own two cents:
- It's trivial to implement a pipe() function or a Pipe class
- A Pipe class is better than both a function and built-in operator,
since it can be configured with custom behaviour, e.g. stop or throw
on empty payload, or repeat on a collection, or even with parallelism
or concurrency- If I had voting rights, I'd vote in favor in a pipe operator :)
From my recollection, there were a couple of things involved.
- It was intended to pair with the PFA RFC, which didn't pass, which
made
it a bit less compelling.- It was close to the RFC deadline, and it seems people get squeamish
around that.- Some folks wanted Hack-style pipes instead of the pipes used by
every
other language with pipes. I've written before on why that's a worse
design.- Arguments that it can be done in user space, which is not true, as I
have
a user-space implementation and it's comparatively cumbersome and
definitely
slower than a native operator would be.- General "meh" attitude on FP features in general from some people.
Side note to Olle: If you want a customizable pipe, you've just
described
a
Monad. :-) It's literally "contextually-sensitive func concatenation."
A
monadic bind operator would be harder to do with PHP's weaker type
system,
but there are ways it could be done.Mm I don't really agree with that, I think monads make sense only in
languages which support them syntactically. A Pipe class is a very
straight-forward construction, and the blog posts I've read about
monads in PHP don't look pretty at all; lots of syntactic noise going
on. But that's another discussion... :)At its most basic:
class Foo {
public function __construct(public readonly mixed $val) {}public __bind(callable $c) {
return $c($this->val);
}
}new Foo('beep') >>= func(...);
Where >>= is the operator that translates to "Call __bind() on the LHS
object with the callable on the RHS." Poof, we now have a monad
operator.
This one is effectively the same as |> as it doesn't do anything, but you
can do whatever you want in the __bind() method.
You also want syntactic sugar around this to "flatten" monadic
usage; in OCaml it'slet*
.This way you won't get read-process-write pipelines as first-class
values, which you can do if you just make a Pipe class instead. I
think. Correct me if I'm wrong. :)Olle
So. Let's get back to pipelines.
I wrote this email because I was manipulating a robots meta tag string:
$robots_array = explode(', ', $robots_string);
$robots_array = array_diff($robots_array, $remove);
$robots_string = implode(', ', $robots_array);Very pipeline-ish.
You could write a userspace pipeline
https://gist.github.com/chx/6638aba76d8b414ffedc7e5af78fb479 but this has
the advantage of being spectacularly ugly and slow as well.Just converting it into a pipeline with
$robots_string
|> fn ($x) => explode(', ', $x)
|> fn ($x) => array_diff($x, $remove)
|> fn ($x) => implode(', ', $x)has the same characteristics. What I would love to see:
$robots_string
|> explode(', ', $)
|> array_diff($, $remove)
|> implode(', ', $)While that does look like partial function application, it does not need to
be. It could be just syntactic sugar. Namely, in the expression following
|> a $ is replaced by the entire expression before the |>. Step by step:explode(', ', $robots_string)
|> array_diff($, $remove)
|> implode(', ', $)array_diff(explode(', ', $robots_string), $remove)
|> implode(', ', $)implode(', ', array_diff(explode(', ', $robots_string), $remove))
We could use any other symbol but I liked the $ sign here because the
entire point is not needing a temporary variable between steps so the $
sign stands for "unnamed temporary variable". I also believe it can't lead
to any ambiguous situation (famous last words).What do you think of this simple version?
That's what Hack does, and what no other language does. I dislike it as it's actually much less flexible overall. It's also more work to implement.
The original idea was that combining real PFA with a callable-based pipe would give us the same net effect if we wanted it, but with greater flexibility and more power across the whole language. Unfortunately the PFA RFC barely didn't pass, due to its complexity. A nicer implementation, if it could be found, would probably pass.
--Larry Garfield
My favorite pipeline is Elixir, where "The pipe operator |> passes the
result of an expression as the first parameter of another expression".. But
it works there because unlike with PHP, it's almost always the first
argument you want. If it's not the first argument you needed to do some
tricks which was only cleaned up in 2021 so now they do:
5
|> then(&Enum.take(1..10, &1))
|> Enum.reverse()
but I digress.
My problem here is... we actually need something that passes the RFC vote.
What flexibility is missing here?
On Tue, Jul 18, 2023 at 6:48 PM Larry Garfield larry@garfieldtech.com
wrote:
So. Let's get back to pipelines.
I wrote this email because I was manipulating a robots meta tag string:
$robots_array = explode(', ', $robots_string);
$robots_array = array_diff($robots_array, $remove);
$robots_string = implode(', ', $robots_array);Very pipeline-ish.
You could write a userspace pipeline
https://gist.github.com/chx/6638aba76d8b414ffedc7e5af78fb479 but this
has
the advantage of being spectacularly ugly and slow as well.Just converting it into a pipeline with
$robots_string
|> fn ($x) => explode(', ', $x)
|> fn ($x) => array_diff($x, $remove)
|> fn ($x) => implode(', ', $x)has the same characteristics. What I would love to see:
$robots_string
|> explode(', ', $)
|> array_diff($, $remove)
|> implode(', ', $)While that does look like partial function application, it does not need
to
be. It could be just syntactic sugar. Namely, in the expression following
|> a $ is replaced by the entire expression before the |>. Step by step:explode(', ', $robots_string)
|> array_diff($, $remove)
|> implode(', ', $)array_diff(explode(', ', $robots_string), $remove)
|> implode(', ', $)implode(', ', array_diff(explode(', ', $robots_string), $remove))
We could use any other symbol but I liked the $ sign here because the
entire point is not needing a temporary variable between steps so the $
sign stands for "unnamed temporary variable". I also believe it can't
lead
to any ambiguous situation (famous last words).What do you think of this simple version?
That's what Hack does, and what no other language does. I dislike it as
it's actually much less flexible overall. It's also more work to implement.The original idea was that combining real PFA with a callable-based pipe
would give us the same net effect if we wanted it, but with greater
flexibility and more power across the whole language. Unfortunately the
PFA RFC barely didn't pass, due to its complexity. A nicer implementation,
if it could be found, would probably pass.--Larry Garfield
My favorite pipeline is Elixir, where "The pipe operator |> passes the
result of an expression as the first parameter of another expression".. But
it works there because unlike with PHP, it's almost always the first
argument you want. If it's not the first argument you needed to do some
tricks which was only cleaned up in 2021 so now they do:5
|> then(&Enum.take(1..10, &1))
|> Enum.reverse()but I digress.
My problem here is... we actually need something that passes the RFC vote.
What flexibility is missing here?
Please don't top post.
The main flexibility for callables is the ability to build a closure on the fly. For example, using $$ (which Hack does, and I insist on calling T_BLING) means that for array and string functions you still need to worry about parameter order. With a callable, you can effectively emulate Elixir's behavior if you want.
For example, my FP library has a whole bunch of higher order functions that just return pipe-ready functions:
https://github.com/Crell/fp/blob/master/src/array.php
Which, with a pipe operator, would allow for:
$result = $arr
|> amap(trim(...))
|> afilter(is_int(...))
|> reduce(0, $fn)
|> trim(...) // At this point it's a string and trim()
is already unary, so we can just use it.
;
Which is pretty nice, IMO. It "flows" nicely, supports both higher order functions and direct calls equally well, and allows for all kinds of further expansion that I haven't thought of yet.
The Hack style would require thinking about the fact that array_map()
and array_filter()
take their arguments in reverse order from each other, which is just silly. This way, that problem goes away entirely.
It also allows a pipe to be used as a quasi alternative to scalar methods, because (if we set aside visibility for the moment) a method is just a function with an implicit $this parameter. Callables and higher order functions make that a lot cleaner.
The caveat is that it does add an extra step to use many of the existing array or string stdlib functions. However, that's work that can be done once. The link above is under 500 lines, heavily documented, and covers every array use case I've run into so far.
Related improvements:
- Writing a higher order function right now is rather cumbersome. There were two previous RFCs that would have made that vastly nicer (short-functions and auto-capture closures), but neither passed. They would have allowed for:
function amap(callable $c) => fn (iterable $it): array {
if (is_array($it)) {
return array_map($c, $it);
}
$result = [];
foreach ($it as $k => $v) {
$result[$k] = $c($v);
}
return $result;
};
Which I think is pretty nice.
- PFA would give us the best of both worlds, as if you wanted you could do:
$result = $arr
|> array_map(trim(...), ?)
|> array_filter(?, is_int(...))
|> array_reduce(?, $fn, 0)
|> trim(...)
;
So with PFA and callable pipes, we get both options. If pipe was just Hack-style, there would be no way to use higher order functions with it.
So IMO, the ideal experience that gets almost everyone what they want would be if all four of the following had passed:
https://wiki.php.net/rfc/short-functions
https://wiki.php.net/rfc/auto-capture-closure
https://wiki.php.net/rfc/partial_function_application
https://wiki.php.net/rfc/pipe-operator-v2
They are all mutually-complementary, but also useful on their own, and were designed that way deliberately. Sadly, all four were declined.
So yeah, it's great that there's renewed interest in pipes, but I've already sunk a ton of time into this space (as have many other people that helped with those RFCs) with nothing to show for it. Unless there's indication from a notable number of voters that they'd support it now, I'm reluctant to sink much more of my time (or anyone else's time) into something that has so far just run into brick walls.
If any of the above has convinced someone that we should try any of these features again, please speak up. I seriously do want this functionality, but OSS spec work is not my favorite way to spend time.
--Larry Garfield
2023-07-18 20:08 GMT+02:00, Larry Garfield larry@garfieldtech.com:
My favorite pipeline is Elixir, where "The pipe operator |> passes the
result of an expression as the first parameter of another expression"..
But
it works there because unlike with PHP, it's almost always the first
argument you want. If it's not the first argument you needed to do some
tricks which was only cleaned up in 2021 so now they do:5
|> then(&Enum.take(1..10, &1))
|> Enum.reverse()but I digress.
My problem here is... we actually need something that passes the RFC
vote.What flexibility is missing here?
Please don't top post.
The main flexibility for callables is the ability to build a closure on the
fly. For example, using $$ (which Hack does, and I insist on calling
T_BLING) means that for array and string functions you still need to worry
about parameter order. With a callable, you can effectively emulate
Elixir's behavior if you want.For example, my FP library has a whole bunch of higher order functions that
just return pipe-ready functions:https://github.com/Crell/fp/blob/master/src/array.php
Which, with a pipe operator, would allow for:
$result = $arr
|> amap(trim(...))
|> afilter(is_int(...))
|> reduce(0, $fn)
|> trim(...) // At this point it's a string andtrim()
is already unary,
so we can just use it.
;Which is pretty nice, IMO. It "flows" nicely, supports both higher order
functions and direct calls equally well, and allows for all kinds of further
expansion that I haven't thought of yet.The Hack style would require thinking about the fact that
array_map()
and
array_filter()
take their arguments in reverse order from each other, which
is just silly. This way, that problem goes away entirely.It also allows a pipe to be used as a quasi alternative to scalar methods,
because (if we set aside visibility for the moment) a method is just a
function with an implicit $this parameter. Callables and higher order
functions make that a lot cleaner.The caveat is that it does add an extra step to use many of the existing
array or string stdlib functions. However, that's work that can be done
once. The link above is under 500 lines, heavily documented, and covers
every array use case I've run into so far.Related improvements:
- Writing a higher order function right now is rather cumbersome. There
were two previous RFCs that would have made that vastly nicer
(short-functions and auto-capture closures), but neither passed. They would
have allowed for:function amap(callable $c) => fn (iterable $it): array {
if (is_array($it)) {
return array_map($c, $it);
}
$result = [];
foreach ($it as $k => $v) {
$result[$k] = $c($v);
}
return $result;
};Which I think is pretty nice.
- PFA would give us the best of both worlds, as if you wanted you could do:
$result = $arr
|> array_map(trim(...), ?)
|> array_filter(?, is_int(...))
|> array_reduce(?, $fn, 0)
|> trim(...)
;
Damn, that PFA RFC would really help with removing noise, instead of
wrapping with fn ($x) => ... everywhere. Shame it didn't pass. :/ 29
vs 20 votes.
Olle
https://wiki.php.net/rfc/short-functions
https://wiki.php.net/rfc/auto-capture-closure
https://wiki.php.net/rfc/partial_function_application
https://wiki.php.net/rfc/pipe-operator-v2
The only thing I can think of is if we gather a team of ~20 ppl and come up
with a plan on collectively getting Voting Karma so that we can have these
awesomeness revisited.
--
Marco Deleu
2023-07-18 21:44 GMT+02:00, Deleu deleugyn@gmail.com:
https://wiki.php.net/rfc/short-functions
https://wiki.php.net/rfc/auto-capture-closure
https://wiki.php.net/rfc/partial_function_application
https://wiki.php.net/rfc/pipe-operator-v2The only thing I can think of is if we gather a team of ~20 ppl and come up
with a plan on collectively getting Voting Karma so that we can have these
awesomeness revisited.
Or, we gather the main concern(s) of the no-voters and try to approach
those? :) Put them in a sheet (Google or Excel) and try to make sense
out of it, if there's a way forward.
Olle
I am glad this topic arose! I was also planning to write on this topic to
the internals mailing list, but have abandoned this idea, because I feel it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively participate in
the development itself.
My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!
There is a popular library github.com/schmittjoh/php-option, which has 250
MILLION installations. Basically what it provides is a class-wrapper of a
value of lack thereof: it's either Some<Type>
or None
. I also maintain
a similar library https://packagist.org/packages/someniatko/result-type
which fixes some shortcomings of the original one related to the static
analysis, but this is another story. Basically what the stats tell us is
that such stuff is popular among the PHP community.
In my eyes, it is actually semantically equivalent to the nullable PHP
types: ?Type
. And some operations provided by the lib, are actually
covered by PHP itself, which has multiple null-friendly operators:
$option->getOrElse($defaultVal)
--> $nullable ?? $defaultVal
$option->isEmpty()
--> $nullable === null
$option->getOrThrow(new \Exception('blah'))
--> $nullable ?? throw new \Exception('blah')
I'd like to use the arguably "more idiomatic" native PHP nullables, rather
than a foreign-feeling userspace construct, if they were more convenient.
But there is a very important operation, map()
, which is unfortunately
not covered by the native PHP, which is Option::map()
, and here is a
real-world example:
return $repository->getById($idFromHttpRequest)
->map($serializer->serializeToJson(...)) // only executes when the
Option is Some, not None
->map(fn (string $json) => new Response(status: 200, content: $json))
->getOrElse(new Response(status: 404));
I'd really like to write such code in a native way:
return $repository->getById($idFromHttpRequest)
?|> $serializer->serializeToJson($$)
?|> new Response(status: 200, content: $$)
?? new Response(status: 404);
Notice I use the old syntax here, using $$
as a "piped" intermediate
result variable, mostly because I'd like to use arbitrary PHP expressions,
instead of piping functions only. I feel this is more natural for the PHP
than the more functional-oriented way. I am okay with using functions too
like this:
return $repository->getById($idFromHttpRequest)
?|> $serializer->serializeToJson(...)
?|> fn (string $json) => new Response(status: 200, content: $json)
?? new Response(status: 404);
but that feels less "clean", and also note the inconsistency with ?? new Response(status: 404)
part.
Regards,
Illia / someniatko
2023-07-18 14:48 GMT+02:00, someniatko someniatko@gmail.com:
I am glad this topic arose! I was also planning to write on this topic to
the internals mailing list, but have abandoned this idea, because I feel it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively participate in
the development itself.My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!There is a popular library github.com/schmittjoh/php-option, which has 250
MILLION installations. Basically what it provides is a class-wrapper of a
value of lack thereof: it's eitherSome<Type>
orNone
.
I'd just like to mention that the Option type comes from FP and it's
not necessarily needed in PHP-land where we have flow-sensitive type
checkers like Psalm (a nullable type ?int
or such is refined to just
int
after a null check). Flow-sensitive type-checking was not
invented when the Option type was first created, so adding it to PHP
is basically stepping back in time, from my point of view.
Olle
2023-07-18 14:48 GMT+02:00, someniatko someniatko@gmail.com:
I am glad this topic arose! I was also planning to write on this topic to
the internals mailing list, but have abandoned this idea, because I feel it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively participate in
the development itself.My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!There is a popular library github.com/schmittjoh/php-option, which has 250
MILLION installations. Basically what it provides is a class-wrapper of a
value of lack thereof: it's eitherSome<Type>
orNone
.I'd just like to mention that the Option type comes from FP and it's
not necessarily needed in PHP-land where we have flow-sensitive type
checkers like Psalm (a nullable type?int
or such is refined to just
int
after a null check). Flow-sensitive type-checking was not
invented when the Option type was first created, so adding it to PHP
is basically stepping back in time, from my point of view.
As I've written before[1], there's a lot of overlap in different error-flow-control options. PHP at the moment leans heavily on the "null is error and build tooling around that" approach, which has its pros (mainly simplicity) and cons (lack of information). That mostly obviates the need for a Maybe type, but not entirely. Right now, there is no "null pipeline" option in the language like someniako is describing.
In some sense, that's also trivial to do in user-space[2], which leads easily to
$foo
|> maybe(bar(...))
|> maybe(baz(...))
|> maybe(beep(...));
Which isn't terrible, but also isn't ideal. I don't think I'd oppose a nullsafe pipe if there's interest (though again, we need interest in pipes in general first).
However, that runs into the related problem that Maybe is... the weaker way of handling error context. In most cases I prefer a Result/Either type, which doesn't map cleanly to null handling. That could be done as a user-space monad (as above, with just a class and method or a magic method and dedicated operator). Doing it more natively would require deeper language hooks. For instance, Rust has the ? suffix on any expression, which means "if this evaluates to an Error case Result, just return that Result." That wouldn't fit well in PHP, though.
As I noted in [1], one option would be to allow an object to flag itself as an error object, and then null-check operators would treat that as null. So given $result = foo($a)->bar(), if foo() returns an error object then $result is assigned to that error object and bar() is never called. I'm still not sure if I like that approach myself, but am open to hear what others think.
To get back on topic, I do think a pipe operator and concat operator are valuable in their own right, and have ample use cases even without mixing in monads or nullables or things like that. We should add them. We can consider adding more robust branching options as well, (be that >>=, ?|>, or something else), but be aware that beyond the very trivial case that will necessarily involve user-space code for many use cases and we'll need to support that. (Which basically comes down to >>= and making monadic classes easier to work with, but also runs into the generics problem, and that's its own mess.)
[1] https://peakd.com/hive-168588/@crell/much-ado-about-null
[2] https://github.com/Crell/fp/blob/master/src/composition.php#L39
--Larry Garfield
PHP at the moment leans heavily on the "null is error and build tooling around that" approach
Yep, you should never use NULL
in your code, the following is catastrophically bad...
$search = ($_GET['q'] ?? NULL);
$search = (isset($_GET['q']) ? $_GET['q'] : NULL); // Pre PHP 7
$search = filter_input(INPUT_GET, 'q');
$search = $request->input('q'); // Laravel
$search = $request->get('q'); // Symfony
$search = $this->request->getQuery('q'); // CakePHP
$search = $request->getGet('q'); // CodeIgniter
$search = json_decode('{"q":null}');
And all fields in the database must be set to NOT NULL, never use things like LEFT JOIN, etc... :-(
Craig
2023-07-18 17:13 GMT+02:00, Larry Garfield larry@garfieldtech.com:
2023-07-18 14:48 GMT+02:00, someniatko someniatko@gmail.com:
I am glad this topic arose! I was also planning to write on this topic
to
the internals mailing list, but have abandoned this idea, because I feel
it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively participate
in
the development itself.My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!There is a popular library github.com/schmittjoh/php-option, which has
250
MILLION installations. Basically what it provides is a class-wrapper of
a
value of lack thereof: it's eitherSome<Type>
orNone
.I'd just like to mention that the Option type comes from FP and it's
not necessarily needed in PHP-land where we have flow-sensitive type
checkers like Psalm (a nullable type?int
or such is refined to just
int
after a null check). Flow-sensitive type-checking was not
invented when the Option type was first created, so adding it to PHP
is basically stepping back in time, from my point of view.As I've written before[1], there's a lot of overlap in different
error-flow-control options. PHP at the moment leans heavily on the "null is
error and build tooling around that" approach, which has its pros (mainly
simplicity) and cons (lack of information). That mostly obviates the need
for a Maybe type, but not entirely. Right now, there is no "null pipeline"
option in the language like someniako is describing.In some sense, that's also trivial to do in user-space[2], which leads
easily to$foo
|> maybe(bar(...))
|> maybe(baz(...))
|> maybe(beep(...));Which isn't terrible, but also isn't ideal. I don't think I'd oppose a
nullsafe pipe if there's interest (though again, we need interest in pipes
in general first).However, that runs into the related problem that Maybe is... the weaker way
of handling error context. In most cases I prefer a Result/Either type,
which doesn't map cleanly to null handling. That could be done as a
user-space monad (as above, with just a class and method or a magic method
and dedicated operator). Doing it more natively would require deeper
language hooks. For instance, Rust has the ? suffix on any expression,
which means "if this evaluates to an Error case Result, just return that
Result." That wouldn't fit well in PHP, though.As I noted in [1], one option would be to allow an object to flag itself as
an error object, and then null-check operators would treat that as null. So
given $result = foo($a)->bar(), if foo() returns an error object then
$result is assigned to that error object and bar() is never called. I'm
still not sure if I like that approach myself, but am open to hear what
others think.To get back on topic, I do think a pipe operator and concat operator are
valuable in their own right, and have ample use cases even without mixing in
monads or nullables or things like that. We should add them. We can
consider adding more robust branching options as well, (be that >>=, ?|>, or
something else), but be aware that beyond the very trivial case that will
necessarily involve user-space code for many use cases and we'll need to
support that. (Which basically comes down to >>= and making monadic classes
easier to work with, but also runs into the generics problem, and that's its
own mess.)[1] https://peakd.com/hive-168588/@crell/much-ado-about-null
[2] https://github.com/Crell/fp/blob/master/src/composition.php#L39--Larry Garfield
Any comment on a pipe (or piped process) as a first-class value? When
you use |> the functions are immediately applied, which might not be
what you want. You can lazify it by wrapping it in a lambda, but I
think that decreases the value of the pipe concept in itself. One
strength is the ability to split the decision about what to glue
together from the decision on how to execute it. You kinda get some
light-weight metaprogramming ability that way.
Well i guess such a pipe-concept is pretty far removed from the simple
pipe operator. :) And also already possible with existing OOP
concepts, callable classes, and now the (...) syntax.
Olle
Any comment on a pipe (or piped process) as a first-class value? When
you use |> the functions are immediately applied, which might not be
what you want. You can lazify it by wrapping it in a lambda, but I
think that decreases the value of the pipe concept in itself. One
strength is the ability to split the decision about what to glue
together from the decision on how to execute it. You kinda get some
light-weight metaprogramming ability that way.Well i guess such a pipe-concept is pretty far removed from the simple
pipe operator. :) And also already possible with existing OOP
concepts, callable classes, and now the (...) syntax.
That's the function concat operator I have been mentioning. Concat can be implemented in terms of pipe, and pipe can be implemented in terms of concat, but I'd rather have both natively.
For more on my thoughts there, see: https://peakd.com/hive-168588/@crell/aoc2021-review
--Larry Garfield
2023-07-18 18:50 GMT+02:00, Larry Garfield larry@garfieldtech.com:
Any comment on a pipe (or piped process) as a first-class value? When
you use |> the functions are immediately applied, which might not be
what you want. You can lazify it by wrapping it in a lambda, but I
think that decreases the value of the pipe concept in itself. One
strength is the ability to split the decision about what to glue
together from the decision on how to execute it. You kinda get some
light-weight metaprogramming ability that way.Well i guess such a pipe-concept is pretty far removed from the simple
pipe operator. :) And also already possible with existing OOP
concepts, callable classes, and now the (...) syntax.That's the function concat operator I have been mentioning. Concat can be
implemented in terms of pipe, and pipe can be implemented in terms of
concat, but I'd rather have both natively.For more on my thoughts there, see:
https://peakd.com/hive-168588/@crell/aoc2021-review
Right, I think I was thinking more of the pipeline design pattern than
the pipe operator. The concat operator will tell you THAT two
functions have been glued together, but you have no way to affect HOW
they are glued together. With the pipeline design pattern, you do: For
example, you could add a logger that logs each input and output in the
Pipe class without much hassle.
The pipeline design pattern could still greatly benefit from some of
these RFCs tho, especially partial function application, I think.
Olle
2023-07-18 18:50 GMT+02:00, Larry Garfield larry@garfieldtech.com:
Any comment on a pipe (or piped process) as a first-class value? When
you use |> the functions are immediately applied, which might not be
what you want. You can lazify it by wrapping it in a lambda, but I
think that decreases the value of the pipe concept in itself. One
strength is the ability to split the decision about what to glue
together from the decision on how to execute it. You kinda get some
light-weight metaprogramming ability that way.Well i guess such a pipe-concept is pretty far removed from the simple
pipe operator. :) And also already possible with existing OOP
concepts, callable classes, and now the (...) syntax.That's the function concat operator I have been mentioning. Concat can be
implemented in terms of pipe, and pipe can be implemented in terms of
concat, but I'd rather have both natively.For more on my thoughts there, see:
https://peakd.com/hive-168588/@crell/aoc2021-reviewRight, I think I was thinking more of the pipeline design pattern than
the pipe operator. The concat operator will tell you THAT two
functions have been glued together, but you have no way to affect HOW
they are glued together. With the pipeline design pattern, you do: For
example, you could add a logger that logs each input and output in the
Pipe class without much hassle.The pipeline design pattern could still greatly benefit from some of
these RFCs tho, especially partial function application, I think.Olle
OK, terminology mismatch. What you're calling "pipeline design pattern" I'm calling "that's literally what a monad is." :-) (More precisely, monads are the mathematical rules that a "fancy function concatenation" operation has to follow in order to still be composable cleanly. You could make pipeline-like things that don't follow those rules, but they'd be less useful.)
Your logging example is literally the "writer monad," one of the standard monad examples.
--Larry Garfield
2023-07-18 14:48 GMT+02:00, someniatko someniatko@gmail.com:
I am glad this topic arose! I was also planning to write on this topic to
the internals mailing list, but have abandoned this idea, because I feel it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively participate in
the development itself.My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!There is a popular library github.com/schmittjoh/php-option, which has 250
MILLION installations. Basically what it provides is a class-wrapper of a
value of lack thereof: it's eitherSome<Type>
orNone
. I also maintain
a similar library https://packagist.org/packages/someniatko/result-type
which fixes some shortcomings of the original one related to the static
analysis, but this is another story. Basically what the stats tell us is
that such stuff is popular among the PHP community.In my eyes, it is actually semantically equivalent to the nullable PHP
types:?Type
. And some operations provided by the lib, are actually
covered by PHP itself, which has multiple null-friendly operators:
$option->getOrElse($defaultVal)
-->$nullable ?? $defaultVal
$option->isEmpty()
-->$nullable === null
$option->getOrThrow(new \Exception('blah'))
-->$nullable ?? throw new \Exception('blah')
I'd like to use the arguably "more idiomatic" native PHP nullables, rather
than a foreign-feeling userspace construct, if they were more convenient.But there is a very important operation,
map()
, which is unfortunately
not covered by the native PHP, which isOption::map()
, and here is a
real-world example:return $repository->getById($idFromHttpRequest) ->map($serializer->serializeToJson(...)) // only executes when the Option is Some, not None ->map(fn (string $json) => new Response(status: 200, content: $json)) ->getOrElse(new Response(status: 404));
Ehm, wouldn't that be the same as a Pipe class that's configured to
stop on null?
public function getThing($id) {
return new Pipe(
$this->getData(...),
$this->serializeData(...),
$this->mapToResponse(...)
)
->stopOnEmpty()
->from($id)
->run();
}
Wait, are you using map() for arrays or not? Looks like not.
Olle
2023-07-18 14:48 GMT+02:00, someniatko someniatko@gmail.com:
I am glad this topic arose! I was also planning to write on this topic to
the internals mailing list, but have abandoned this idea, because I feel it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively participate in
the development itself.My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!There is a popular library github.com/schmittjoh/php-option, which has 250
MILLION installations. Basically what it provides is a class-wrapper of a
value of lack thereof: it's eitherSome<Type>
orNone
. I also maintain
a similar library https://packagist.org/packages/someniatko/result-type
which fixes some shortcomings of the original one related to the static
analysis, but this is another story. Basically what the stats tell us is
that such stuff is popular among the PHP community.In my eyes, it is actually semantically equivalent to the nullable PHP
types:?Type
. And some operations provided by the lib, are actually
covered by PHP itself, which has multiple null-friendly operators:
$option->getOrElse($defaultVal)
-->$nullable ?? $defaultVal
$option->isEmpty()
-->$nullable === null
$option->getOrThrow(new \Exception('blah'))
-->$nullable ?? throw new \Exception('blah')
I'd like to use the arguably "more idiomatic" native PHP nullables, rather
than a foreign-feeling userspace construct, if they were more convenient.But there is a very important operation,
map()
, which is unfortunately
not covered by the native PHP, which isOption::map()
, and here is a
real-world example:return $repository->getById($idFromHttpRequest) ->map($serializer->serializeToJson(...)) // only executes when the Option is Some, not None ->map(fn (string $json) => new Response(status: 200, content: $json)) ->getOrElse(new Response(status: 404));
Ehm, wouldn't that be the same as a Pipe class that's configured to
stop on null?public function getThing($id) {
return new Pipe(
$this->getData(...),
$this->serializeData(...),
$this->mapToResponse(...)
)
->stopOnEmpty()
->from($id)
->run();
}Wait, are you using map() for arrays or not? Looks like not.
Olle
--
To unsubscribe, visit: https://www.php.net/unsub.php
I might be venting, but, I wish the operators RFC had passed...
On Tue, Jul 18, 2023 at 8:05 AM Robert Landers landers.robert@gmail.com
wrote:
On Tue, Jul 18, 2023 at 3:18 PM Olle Härstedt olleharstedt@gmail.com
wrote:2023-07-18 14:48 GMT+02:00, someniatko someniatko@gmail.com:
I am glad this topic arose! I was also planning to write on this topic
to
the internals mailing list, but have abandoned this idea, because I
feel it
might be inconvenient for the real active PHP developers on the list to
receive too many emails from the people which don't actively
participate in
the development itself.My interest in the pipe operator might seem a little non-standard -
basically what I'd really want to see is a nullable pipe operator!There is a popular library github.com/schmittjoh/php-option, which
has 250
MILLION installations. Basically what it provides is a class-wrapper
of a
value of lack thereof: it's eitherSome<Type>
orNone
. I also
maintain
a similar library
https://packagist.org/packages/someniatko/result-type
which fixes some shortcomings of the original one related to the static
analysis, but this is another story. Basically what the stats tell us
is
that such stuff is popular among the PHP community.In my eyes, it is actually semantically equivalent to the nullable PHP
types:?Type
. And some operations provided by the lib, are actually
covered by PHP itself, which has multiple null-friendly operators:
$option->getOrElse($defaultVal)
-->$nullable ?? $defaultVal
$option->isEmpty()
-->$nullable === null
$option->getOrThrow(new \Exception('blah'))
-->$nullable ?? throw new \Exception('blah')
I'd like to use the arguably "more idiomatic" native PHP nullables,
rather
than a foreign-feeling userspace construct, if they were more
convenient.But there is a very important operation,
map()
, which is
unfortunately
not covered by the native PHP, which isOption::map()
, and here is a
real-world example:return $repository->getById($idFromHttpRequest) ->map($serializer->serializeToJson(...)) // only executes when the Option is Some, not None ->map(fn (string $json) => new Response(status: 200, content:
$json))
->getOrElse(new Response(status: 404));
Ehm, wouldn't that be the same as a Pipe class that's configured to
stop on null?public function getThing($id) {
return new Pipe(
$this->getData(...),
$this->serializeData(...),
$this->mapToResponse(...)
)
->stopOnEmpty()
->from($id)
->run();
}Wait, are you using map() for arrays or not? Looks like not.
Olle
--
To unsubscribe, visit: https://www.php.net/unsub.php
I might be venting, but, I wish the operators RFC had passed...
I don't think I will re-propose it with the current set of voters tbh. The
main people who contribute to the Zend folder (the core engine/compiler
parts) were highly skeptical of it, with at least two telling me flat out
that they would vote no even on a hypothetical perfect feature design. Very
few contributors actually touch large parts of that area of the engine, so
most voters tend to take the opinions of those 5-6 voters very seriously on
such proposals. As I don't think it's possible for me to convince those 5-6
people, I don't think it would be a good idea to re-propose, because even
if I did get a 2/3 vote on it, I feel like the people most responsible for
helping me maintain the feature would not be very happy/willing to do so,
and that would probably harm the PHP project in other ways.
Jordan