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