On Fri, Mar 17, 2023, at 9:45 AM, Larry Garfield wrote:
> On Fri, Mar 17, 2023, at 3:54 AM, Michał Marcin Brzuchalski wrote:
>
>>> As a thought experiment, if we had that syntax and functions that were
>>> designed to be used with them, it would look like so:
>>>
>>> function amap(callable $c, iterable $it) { ... }
>>> function implode(string $sep, iterable $it) { ... }
>>> function length(string $s) { ... }
>>>
>>> $arr = [1, 2, 3];
>>>
>>> $a2 = amap(...)->partial(chr(...))($arr);
>>>
>>> $str = implode(...)->partial(',')($a2);
>>>
>>> Or, if combined with pipes:
>>>
>>> $size = $arr
>>> |> amap(...)->partial(chr(...))
>>> |> implode(...)->partial(',')
>>> |> length(...);
>>>
>>> Which... is not terrible, especially as it doesn't preclude using higher
>>> order functions for more control.
>>>
>>
>> Maybe we could introduce two additional methods on a Closure similar to
>> what Java have
>> https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html
>> * andThen() - which functionality is like a pipe operator
>> * apply() - which you can call without the option to bind/rebind and just
>> pass arguments for execution
>>
>> The pipe operator can be introduced later, but we could already have the
>> functionality on Closure.
>>
>> The above example might look readable as well:
>>
>> $size = amap(...)->partial(chr(...))
>>     ->andThen(implode(...)->partial(','))
>>     ->andThen(length(...))
>>     ->apply($arr);
>
> See that brings up a subtle difference between two operations: pipe and 
> compose.
>
> Compose takes two functions A and B and returns a new function that is 
> logically identical to B(A()).  (Or sometimes A(B()), depending on the 
> language, which is all kinds of confusing.)
>
> Pipe takes an arbitrary value and unary function and calls the function 
> with that value immediately, returning the result.
>
> You can build a pipe equivalent out of compose, although it's a bit 
> clunky and the semantics are not *quite* identical.  (The order of 
> execution is different, which may or may not matter depending on the 
> specifics of each call.)
>
> In the example above, andThen() is acting as a compose operator, while 
> apply() is just boring function application.  It works but it's a bit 
> clunky compared to a proper pipe.  That said, there are user space 
> libraries that do that.
>
> Thinking aloud...  I said before it would be better to have a native 
> operator for all of these.  (compose, pipe, and partial.)  If we 
> restrict ourselves to Closure objects rather than all callables (since 
> callables are a lot messier), that does open up some new options.  
> Specifically, the following are already syntax errors today:
>
> $c = strlen(...);
> $d = array_map(...);
>
> $c + $d; // not allowed.
> $c . $d; // not allowed, thinks it's string concat and $c isn't stringable.
> $c[2, 3]; // not allowed.
> $c{5}; // not allowed, thinks it's a string offset.
>
> So that suggests to me the following:
>
> * Define $a + $b on closure objects to be a compose operator that 
> returns a new function equivalent to $b($a( )).  It would only work on 
> unary functions, validate compatible types between the param and return 
> values, and have the correct type information.
>
> * Define $c{ ... } as a "partial application" call.  The {} body would 
> be similar to a function call now; or maybe the original PFA syntax 
> with ? and ... ?  Debatable, but the {} would be a better signal to the 
> engine that we're not calling a function, just partially calling.  It's 
> also reasonably self-evident to developers.
>
> Those two together would allow for this:
>
> (amap{chr(...), ?} + implode{?, separator: ','} + length(...))($arr);
>
> Which is... pretty nice, really.  It's very close to the original PFA 
> syntax and capabilities, compact, not easily confused with anything 
> else, type safe, and the engine can handle optimizing around not having 
> unnecessary interstitial functions internally.
>
> I still think we should also add a pipe operator |> as well, but that 
> would be just as compatible with the {}-based PFA syntax.  It's also 
> fairly trivial to implement.
>
> $size = $arr
>   |> amap{chr(...), ?)
>   |> implode(',', ?)
>   |> length(...);
>
> Thoughts?

Wait, my examples were wrong.  I forgot that we're talking about only Closures. 
 They should be:

> (amap(...){chr(...), ?} + implode(...){?, separator: ','} + 
> length(...))($arr);

> $size = $arr
>   |> amap(...){chr(...), ?)
>   |> implode(...)(',', ?)
>   |> length(...);

Not quite as nice, but still not terrible.

--Larry Garfield

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: https://www.php.net/unsub.php

Reply via email to