73

[RFC] Pre-draft for PipeOp v2 - Externals

 6 years ago
source link: https://externals.io/message/100706
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

[RFC] Pre-draft for PipeOp v2

[RFC] Pre-draft for PipeOp v2

Sara Golemon Posted 3 years ago by Sara Golemonview source

unread

I was planning to update the RFC, but wiki.php.net is having issues
atm and isn't coming back up with basic coaxing, so I'll just start
discussion informally, and the RFC can be updates later.

Background: I made an RFC some time ago to implement HackLang's Pipe
Operator https://docs.hhvm.com/hack/operators/pipe-operator which
provides fluent calling for non-object interfaces.
I circulated it to mixed reviews, many negative, with the negativity
feeling like it centered on the use of a magic placeholder token $$.

After discussion with Levi and others who suggested a simpler
approach, I'd like to offer
https://github.com/php/php-src/compare/master...sgolemon:pipe2 as an
alternate possibility.

This version removes the $$ token, and instead treats the RHS of the
expression as a callable obeying the same rules as the callable
typehint elsewhere in the language. Specifically:

  • Free functions as strings containing the function name. (e.g. 'funcname')
  • Object methods as array($object, 'methodname')
  • Static methods as array('Classname', 'methodname')
  • Closure expression (e.g. function($x) { return ...; } )
  • Object instance with an __invoke() method.

In a given pipe expression, the output of the LHS expression feeds a
single arg to the callable on the RHS.
Examples:

$x = "hello"
|> 'strtoupper'
|> function($x) { return $x . " world"; };
// $x === "HELLO world"

Non-Goal: I didn't include support for base function names (e.g.
"hello" |> strtoupper) because of the conflict with constants.
Using a constant to store your function name is totes legit and
consistent with language syntax.

Future Scope: Short Lambdas $x => $x + 1 and Partial Functions
someFunc('fixed val1', ..., 'fixed val2') would help make this
functionality more useful and are worth discussing as a sub-thread,
but are not required to be implemented at the same time.

-Sara

Levi Morrison Posted 3 years ago by Levi Morrisonview source
unread

I was planning to update the RFC, but wiki.php.net is having issues
atm and isn't coming back up with basic coaxing, so I'll just start
discussion informally, and the RFC can be updates later.

Background: I made an RFC some time ago to implement HackLang's Pipe
Operator https://docs.hhvm.com/hack/operators/pipe-operator which
provides fluent calling for non-object interfaces.
I circulated it to mixed reviews, many negative, with the negativity
feeling like it centered on the use of a magic placeholder token $$.

After discussion with Levi and others who suggested a simpler
approach, I'd like to offer
https://github.com/php/php-src/compare/master...sgolemon:pipe2 as an
alternate possibility.

This version removes the $$ token, and instead treats the RHS of the
expression as a callable obeying the same rules as the callable
typehint elsewhere in the language. Specifically:

  • Free functions as strings containing the function name. (e.g. 'funcname')
  • Object methods as array($object, 'methodname')
  • Static methods as array('Classname', 'methodname')
  • Closure expression (e.g. function($x) { return ...; } )
  • Object instance with an __invoke() method.

In a given pipe expression, the output of the LHS expression feeds a
single arg to the callable on the RHS.
Examples:

$x = "hello"
|> 'strtoupper'
|> function($x) { return $x . " world"; };
// $x === "HELLO world"

Non-Goal: I didn't include support for base function names (e.g.
"hello" |> strtoupper) because of the conflict with constants.
Using a constant to store your function name is totes legit and
consistent with language syntax.

Future Scope: Short Lambdas $x => $x + 1 and Partial Functions
someFunc('fixed val1', ..., 'fixed val2') would help make this
functionality more useful and are worth discussing as a sub-thread,
but are not required to be implemented at the same time.

-Sara

Thank you for making this proposal! As alluded to I had concerns on
the original proposal and fully support this version. Sometimes
simplicity is the mark of a good RFC and I think this is one of those
times.

Nikita Popov Posted 3 years ago by Nikita Popovview source

unread

I was planning to update the RFC, but wiki.php.net is having issues
atm and isn't coming back up with basic coaxing, so I'll just start
discussion informally, and the RFC can be updates later.

Background: I made an RFC some time ago to implement HackLang's Pipe
Operator https://docs.hhvm.com/hack/operators/pipe-operator which
provides fluent calling for non-object interfaces.
I circulated it to mixed reviews, many negative, with the negativity
feeling like it centered on the use of a magic placeholder token $$.

After discussion with Levi and others who suggested a simpler
approach, I'd like to offer
https://github.com/php/php-src/compare/master...sgolemon:pipe2 as an
alternate possibility.

This version removes the $$ token, and instead treats the RHS of the
expression as a callable obeying the same rules as the callable
typehint elsewhere in the language. Specifically:

  • Free functions as strings containing the function name. (e.g. 'funcname')
  • Object methods as array($object, 'methodname')
  • Static methods as array('Classname', 'methodname')
  • Closure expression (e.g. function($x) { return ...; } )
  • Object instance with an __invoke() method.

In a given pipe expression, the output of the LHS expression feeds a
single arg to the callable on the RHS.
Examples:

$x = "hello"
|> 'strtoupper'
|> function($x) { return $x . " world"; };
// $x === "HELLO world"

Non-Goal: I didn't include support for base function names (e.g.
"hello" |> strtoupper) because of the conflict with constants.
Using a constant to store your function name is totes legit and
consistent with language syntax.

Future Scope: Short Lambdas $x => $x + 1 and Partial Functions
someFunc('fixed val1', ..., 'fixed val2') would help make this
functionality more useful and are worth discussing as a sub-thread,
but are not required to be implemented at the same time.

I think this feature makes very little sense if it's not introduced
together with a way of making partial application much more ergonomic than
it is now. I understand the desire to keep things separate, but I don't
think that this proposal can land without partial application syntax being
introduced beforehand or in conjunction with it.

From a previous R11 discussion, I remember that two strong contenders were
foo(...) for getting a callable with an arbitrary number of unbound
parameters and foo($$) for a single unbound parameter. In the latter case
the proposal has a similar expressive power as the previous pipe operator
proposal. Another option was to make $$ a more general construct for
obtaining single-parameter closures in compact form. Then you would also be
able to write "$$->getFoo()" to get the equivalent of "function($x) {
return $x->getFoo(); }" and similar. A disadvantage is that the precedence
here is less clear and that a short closure syntax might be more clear for
the cases that go beyond partial application.

Regards,
Nikita

Derick Rethans Posted 3 years ago by Derick Rethansview source
unread

I was planning to update the RFC, but wiki.php.net is having issues
atm and isn't coming back up with basic coaxing, so I'll just start
discussion informally, and the RFC can be updates later.

<snip>

Non-Goal: I didn't include support for base function names (e.g.
"hello" |> strtoupper) because of the conflict with constants.
Using a constant to store your function name is totes legit and
consistent with language syntax.

Future Scope: Short Lambdas $x => $x + 1 and Partial Functions
someFunc('fixed val1', ..., 'fixed val2') would help make this
functionality more useful and are worth discussing as a sub-thread,
but are not required to be implemented at the same time.

I think this feature makes very little sense if it's not introduced
together with a way of making partial application much more ergonomic than
it is now.

What do you mean here by "partial application"?

cheers,
Derick

Christoph M. Becker Posted 3 years ago by Christoph M. Beckerview source
unread

I think this feature makes very little sense if it's not introduced
together with a way of making partial application much more ergonomic than
it is now.

What do you mean here by "partial application"?

Partial application is a common concept in functional/applicative
programming languages, which facilitates to get a closure over another
function where some of the parameters are already applied, e.g.
something like (pseudocode):

incr = partial(plus, 1);
print incr(2); // 3
print incr(100); // 101

--
Christoph M. Becker

Sara Golemon Posted 3 years ago by Sara Golemonview source

unread

Future Scope: Short Lambdas $x => $x + 1 and Partial Functions
someFunc('fixed val1', ..., 'fixed val2') would help make this
functionality more useful and are worth discussing as a sub-thread,
but are not required to be implemented at the same time.

I think this feature makes very little sense if it's not introduced
together with a way of making partial application much more ergonomic than
it is now.

I generally agree with this statement, and it's why the original
pipe-op (as with hacklang's version) included (a form of) pfa
implicitly. If that means we should nail down the specifics of PFA
and/or Short-Lambdas before even bothering to move into full draft of
PipeOp, then that's fine.

Levi made the very solid argument that they can be independently
considered even if they compliment each other well, and given the
triviality of the pipe half of that equation, I put it together first
so that we have something to look at.

On the topic of pipe2 specifically:
The positive side of this diff is that it has zero impact on the
backend compiler or opcache since it doesn't produce any new AST
elements. On the minus side, the AST it produces opaques the original
format. In practice, this hack is only visible if you're using your
or my ast extensions, or if you use a failing assert. This is what I
like the least about the approach I included in my link. It's not the
first place we've hidden original intent behind AST transformations,
but it's certainly the most visually jarring when serialized.

What do you mean here by "partial application"?

$trimX = trim($$, $x);

In this example, trim isn't invoked immediately, instead, $trimX
becomes a callable (closure) which takes a single argument and invokes
trim() with that argument and the captured $x variable. We've
"partially applied" some arguments to the trim() function, but we
haven't finished forming a function call yet.

Essentially, the above turns into this:
$trimX = function($arg) use ($x) { return trim($arg, $x); };

The '$$' token used by HackLang's PipeOp works well enough because we
know that RHS is only ever going to receive one argument (the result
of LHS). A more generic approach to PFA would probably involve
allowing for multiple positional arguments, perhaps like:

$myInArray = in_array($2, $1); // Reverse the arg order, effectively

PFA and ShortLambdas have the same scope capture issues however, which
make them thornier issues to tackle.

-Sara

Stanislav Malyshev Posted 3 years ago by Stanislav Malyshevview source

unread

After discussion with Levi and others who suggested a simpler
approach, I'd like to offer
https://github.com/php/php-src/compare/master...sgolemon:pipe2 as an
alternate possibility.

I am still feeling it is a bit weird to do things that way, but other
than that (which is just my personal issue) I do not see any problems in
this proposal. With magic weird $$ thing gone, it seems to be
straightforward and mostly intuitive. So while I am not feeling this is
a must, I think it may be nice to have it.

It'd be also nice then if we could have some syntax that allowed us to
refer to functions/methods as callables - mostly for the benefit of the
code readers and IDEs. I.e. you can do "hello" |> "strtoupper" and it's
fine but it is not at all intuitive what you're doing sending one string
into another. Same with "hello" |> [$this, 'bar']. Now, if we could do
something like this:
"hello" |> &{strtoupper}
"hello" |> &{$this->bar}

(yes I know the syntax is ugly, I hope you get the idea though to have a
syntax for this) then you could have better readability, easier IDE
autocompletion and other benefits. Not for this RFC, clearly, but just
putting it out there so I don't forget.

--
Stas Malyshev
[email protected]

Levi Morrison Posted 3 years ago by Levi Morrisonview source
unread

After discussion with Levi and others who suggested a simpler
approach, I'd like to offer
https://github.com/php/php-src/compare/master...sgolemon:pipe2 as an
alternate possibility.

I am still feeling it is a bit weird to do things that way, but other
than that (which is just my personal issue) I do not see any problems in
this proposal. With magic weird $$ thing gone, it seems to be
straightforward and mostly intuitive. So while I am not feeling this is
a must, I think it may be nice to have it.

It'd be also nice then if we could have some syntax that allowed us to
refer to functions/methods as callables - mostly for the benefit of the
code readers and IDEs. I.e. you can do "hello" |> "strtoupper" and it's
fine but it is not at all intuitive what you're doing sending one string
into another. Same with "hello" |> [$this, 'bar']. Now, if we could do
something like this:
"hello" |> &{strtoupper}
"hello" |> &{$this->bar}

(yes I know the syntax is ugly, I hope you get the idea though to have a
syntax for this) then you could have better readability, easier IDE
autocompletion and other benefits. Not for this RFC, clearly, but just
putting it out there so I don't forget.

--
Stas Malyshev
[email protected]

I don't want to get too far off topic but since Stas has wandered off
a bit already I'll mention this. Scala has "placeholders":

val sum = List(1,2,3,4,5).reduceLeft(_+_)

The expression _ + _ will create a function that takes two
parameters and then returns the result of adding them together,
equivalent to this:

val sum = List(1,2,3,4,5).reduceLeft((a, b) => a + b)

The single underscore _ might not work for PHP because gettext has
used a single underscore as an alias for... a long time, maybe since
its inception. But for this example I'll use it anyway:

"hello" |> strtoupper(_) |> $this->bar(_)

It's saying, pretty literally, fill in the blank. Remember, though,
it's actually creating closures and doesn't directly mean "put the
result of the left-hand side here" unlike the previous proposal for
$$.

I brought it up because this can fill the same need as Stas' proposal
creating callables with something like &{strtoupper}. If we used
the symbol $$ for the same placeholder mechanism from Scala it
becomes a more general purpose mechanism that could be used for any
callables, not just ones involved in a pipe:

$potentially_valid_emails =

array_filter($this->isProperlyFormattedEmailAddress($$), $email_list);

Anyway, the purpose of this thread is to talk about the pipe operator
so I'll stop talking now.

Sara Golemon Posted 3 years ago by Sara Golemonview source

unread

It'd be also nice then if we could have some syntax that allowed us to
refer to functions/methods as callables - mostly for the benefit of the
code readers and IDEs. I.e. you can do "hello" |> "strtoupper" and it's
fine but it is not at all intuitive what you're doing sending one string
into another. Same with "hello" |> [$this, 'bar']. Now, if we could do
something like this:
"hello" |> &{strtoupper}
"hello" |> &{$this->bar}

Super-hacky implementation (that I wouldn't want to merge, but it
shows the syntax at work).

https://github.com/php/php-src/compare/master...sgolemon:lambda
which provides a form of both short-closures and partial functions.

Combined with also-super-hacky pipe diff from earlier, you get:

$x = "Hello"
|> &{strtoupper($0)}
|> &{ $0 . "world" }
|> &{strrev($0)};

var_dump($x);
// string(10) "dlrowOLLEH"

-Sara

Levi Morrison Posted 3 years ago by Levi Morrisonview source
unread

On Thu, Sep 21, 2017 at 5:13 PM, Stanislav Malyshev [email protected]
wrote:

It'd be also nice then if we could have some syntax that allowed us to
refer to functions/methods as callables - mostly for the benefit of the
code readers and IDEs. I.e. you can do "hello" |> "strtoupper" and it's
fine but it is not at all intuitive what you're doing sending one string
into another. Same with "hello" |> [$this, 'bar']. Now, if we could do
something like this:
"hello" |> &{strtoupper}
"hello" |> &{$this->bar}

Super-hacky implementation (that I wouldn't want to merge, but it
shows the syntax at work).

https://github.com/php/php-src/compare/master...sgolemon:lambda
which provides a form of both short-closures and partial functions.

Combined with also-super-hacky pipe diff from earlier, you get:

$x = "Hello"
|> &{strtoupper($0)}
|> &{ $0 . "world" }
|> &{strrev($0)};

var_dump($x);
// string(10) "dlrowOLLEH"

-Sara

By the way the & is not required. When experimenting with possible short
closure syntax I created an implementation for {} to make sure it was
possible:

$x = "Hello"
|> {strtoupper($0)}
|> { $0 . "world" }
|> {strrev($0)};

If people would prefer that syntax for short-closures then that's fine by
me. If we made it this concise there wouldn't be a need for a $$ proposal
anyway.


My impression from the community is that the pipe operator is desirable but
not if it encourages string or array callables. In that vein which syntax
do you prefer?

// brace style
$x = "Hello"
|> {strtoupper($0)}
|> { $0 . "world" }
|> {strrev($0)};

// fn style
$x = "Hello"
|> fn($x) => strtoupper($x)}
|> fn($x) => $x . "world"
|> fn($x) => strrev($0);

// caret style
$x = "Hello"
|> ^($x) => strtoupper($x)}
|> ^($x) => $x . "world"
|> ^($x) => strrev($0);

I included these two styles because they are the most promising versions
from the arrow functions discussions. I think my preferred order is the one
I wrote them in. The brace style is concise, nicely delimits the
expression, and seems the clearest for me to read because the symbols don't
dominate. The fn style seems nicer than caret which has too many symbols in
close proximity for my taste.

Community thoughts? Which short closure style pairs the nicest with the
pipe operator?

Rowan Collins Posted 3 years ago by Rowan Collinsview source
unread

The brace style is concise, nicely delimits the
expression, and seems the clearest for me to read because the symbols don't
dominate.

This is something that I tried to push for in previous discussions -
I've never liked the syntaxes where the expression floats away from the
operator, and you have to work out where it ends. The counter-argument I
got was that some people actually like writing things like this,
although I'm not entirely clear why:

fn($x) => fn($y) => in_array($x, $y)

Which brings us to the question of how we might generalise the {} syntax
for more complex situations. Clearly, we can't just nest them if the
first parameter is always called $0:

{ { in_array($0, $0) } }  #WAT?

Obviously named variables are kind of useful anyway, so maybe $0 could
be the default, but allow them to be specified:

// Simple definition
{ $0 . ' world' }

{ $greeting => $greeting . ' world' )

// Nested definitions
{ $x => { in_array($x, $0) } }

{ $x => { $y => in_array($x, $y) } }

// 2-parameter function
{ $x, $y => in_array($x, $y) }

My impression from the community is that the pipe operator is
desirable but
not if it encourages string or array callables.

Yeah, my first thought on this thread that I liked this version of the
proposal significantly less than the previous one, because I always
liked the idea of this being a programming style, with the RHS being a
statement to be rewritten. So you could write $input |> $foo = $$ |>
echo $$ |> return $$ ...

But with a suitable way of creating a callable to pass to it, I could
live with it.

What if the syntax for taking a reference to function was just a special
case of the partial application / lambda syntax? At risk of turning into
Perl, we could use "$..." to mean "copy in all the parameters":

$foo = { in_array($...) };

This would be logically equivalent to:

$foo = function(...$x) { return in_array(...$x); }

But the compiler could actually optimise it as something more like
Closure::fromCallable('in_array')

Just throwing some thoughts out there to see if any of them stick...

--
Rowan Collins
[IMSoP]

Tony Marston Posted 3 years ago by Tony Marstonview source
unread

"Rowan Collins" wrote in message
news:[email protected]...

The brace style is concise, nicely delimits the
expression, and seems the clearest for me to read because the symbols
don't
dominate.

This is something that I tried to push for in previous discussions - I've
never liked the syntaxes where the expression floats away from the
operator, and you have to work out where it ends. The counter-argument I
got was that some people actually like writing things like this, although
I'm not entirely clear why:

fn($x) => fn($y) => in_array($x, $y)

Just because some people would like to write code like this does not make it
acceptable for the majority of the programming community. You should never
forget that the primary aim of a programmer is to write code which can be
read by a human, and only incidentally to be executed by a machine (H.
Abelson and G. Sussman in "The Structure and Interpretation of Computer
Programs", 1984).

Some people complain that PHP is too verbose, so they strive to replace long
words, or groups of words, with abbreviations or even symbols. This, IMHO,
converts a readable program into a bunch of hieroglyphics and should
therefore be avoided.

I think there should be a rule which states that if something can already be
done with 5 lines or less of userland code then it should not be built into
the core language as it would be adding unnecessary complications that would
only benefit a small minority of programmers but would be to the detriment
of the majority.

--
Tony Marston

michal@brzuchalski.com Posted 3 years ago by [email protected]view source

unread

2017-09-28 21:07 GMT+02:00 Levi Morrison [email protected]:

On Thu, Sep 21, 2017 at 5:13 PM, Stanislav Malyshev <[email protected]

wrote:

It'd be also nice then if we could have some syntax that allowed us to
refer to functions/methods as callables - mostly for the benefit of the
code readers and IDEs. I.e. you can do "hello" |> "strtoupper" and it's
fine but it is not at all intuitive what you're doing sending one
string
into another. Same with "hello" |> [$this, 'bar']. Now, if we could do
something like this:
"hello" |> &{strtoupper}
"hello" |> &{$this->bar}

Super-hacky implementation (that I wouldn't want to merge, but it
shows the syntax at work).

https://github.com/php/php-src/compare/master...sgolemon:lambda
which provides a form of both short-closures and partial functions.

Combined with also-super-hacky pipe diff from earlier, you get:

$x = "Hello"
|> &{strtoupper($0)}
|> &{ $0 . "world" }
|> &{strrev($0)};

var_dump($x);
// string(10) "dlrowOLLEH"

-Sara

By the way the & is not required. When experimenting with possible short
closure syntax I created an implementation for {} to make sure it was
possible:

$x = "Hello"
|> {strtoupper($0)}
|> { $0 . "world" }
|> {strrev($0)};

If people would prefer that syntax for short-closures then that's fine by
me. If we made it this concise there wouldn't be a need for a $$ proposal
anyway.

I love this syntax with braces, I wanted to do like it in
https://wiki.php.net/rfc/short-closures
but only for replacement of string or array callables as closures.


My impression from the community is that the pipe operator is desirable but
not if it encourages string or array callables. In that vein which syntax
do you prefer?

// brace style
$x = "Hello"
|> {strtoupper($0)}
|> { $0 . "world" }
|> {strrev($0)};

// fn style
$x = "Hello"
|> fn($x) => strtoupper($x)}
|> fn($x) => $x . "world"
|> fn($x) => strrev($0);

// caret style
$x = "Hello"
|> ^($x) => strtoupper($x)}
|> ^($x) => $x . "world"
|> ^($x) => strrev($0);

I included these two styles because they are the most promising versions
from the arrow functions discussions. I think my preferred order is the one
I wrote them in. The brace style is concise, nicely delimits the
expression, and seems the clearest for me to read because the symbols don't
dominate. The fn style seems nicer than caret which has too many symbols in
close proximity for my taste.

Community thoughts? Which short closure style pairs the nicest with the
pipe operator?

--
regards / pozdrawiam,

Michał Brzuchalski
about.me/brzuchal
brzuchalski.com

Christoph M. Becker Posted 3 years ago by Christoph M. Beckerview source

unread

I was planning to update the RFC, but wiki.php.net is having issues
atm and isn't coming back up with basic coaxing, so I'll just start
discussion informally, and the RFC can be updates later.

Background: I made an RFC some time ago to implement HackLang's Pipe
Operator https://docs.hhvm.com/hack/operators/pipe-operator which
provides fluent calling for non-object interfaces.
I circulated it to mixed reviews, many negative, with the negativity
feeling like it centered on the use of a magic placeholder token $$.

After discussion with Levi and others who suggested a simpler
approach, I'd like to offer
https://github.com/php/php-src/compare/master...sgolemon:pipe2 as an
alternate possibility.

This version removes the $$ token, and instead treats the RHS of the
expression as a callable obeying the same rules as the callable
typehint elsewhere in the language. Specifically:

  • Free functions as strings containing the function name. (e.g. 'funcname')
  • Object methods as array($object, 'methodname')
  • Static methods as array('Classname', 'methodname')
  • Closure expression (e.g. function($x) { return ...; } )
  • Object instance with an __invoke() method.

In a given pipe expression, the output of the LHS expression feeds a
single arg to the callable on the RHS.
Examples:

$x = "hello"
|> 'strtoupper'
|> function($x) { return $x . " world"; };
// $x === "HELLO world"

Non-Goal: I didn't include support for base function names (e.g.
"hello" |> strtoupper) because of the conflict with constants.
Using a constant to store your function name is totes legit and
consistent with language syntax.

I'm not particularly fond of a pipe(line) operator. I don't see a great
advantage over a simple compose function:

$appendWorld = function ($x) {return "$x world";};
$x = compose('strtoupper', $appendWorld)('hello');

The benefit of using a compose function would be the possibility of
point-free programming without explicitely declaring another function:

$toUpperAppendWorld = compose(
'strtoupper',
function ($x) {return "$x world";}
);
$x = $toUpperAppendWorld('hello');
$y = $toUpperAppendWorld('goodbye');

Basically, the compose function would be like the pipe operator without
special casing the (first) input.

See also Andreas's suggestion regarding built-in functions for common
functional primitives (http://news.php.net/php.internals/100739).

--
Christoph M. Becker

Contribute to the project on GitHub: mnapoli/externals

Externals is a serverless application deployed with Bref and sponsored by null ❤

Search sponsored by Algolia

About data: if you login using GitHub, no personal data (or GitHub token) will be stored.
The only thing stored is your GitHub ID, username and which threads/emails you have already read.

Check out my Serverless Visually Explained course.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK