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? --Larry Garfield -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: https://www.php.net/unsub.php