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