On Sun, Oct 30, 2022, at 3:25 PM, Tim Düsterhus wrote:
> Hi
>
> On 10/30/22 17:22, Larry Garfield wrote:
>> 2. There are ample use cases for most operations to return an array or a 
>> lazy iterable.  Both totally exist.  I solved that by also having a separate 
>> version of each function, eg, amap() vs itmap().  The former returned an 
>> array, the latter returned a generator that generated the equivalent array.  
>> It would be a fatal design flaw to not account for this.  Yes, this balloons 
>> the number of such functions, which sucks, but that's PHP for you.
>
> Please not. Only provide functions that return an iterable without 
> giving any further guarantees, because …
>
>> A possible alternative would be to always return a lazy iterable in all 
>> circumstances and assume someone can use to_array() or equivalent on the 
>> result if they want it as an array.  (That's effectively what Python 3 does 
>> with comprehensions.)  However, that could have non-trivial performance 
>> impact since generators are slower than plain arrays.
>
> … this works. The users should not need to think about what return type 
> is more useful for their specific use case.
>
> The heavy lifting / optimization should be left to the compiler / 
> engine, similarly how a fully qualified 'is_null()' is also optimized, 
> such that no actual function call happens [1].
>
> Even if this kind of optimization does not happen in the first version, 
> this should not be a reason to clutter the API surface.

I'm open to this approach, but we should be mindful of the ergonomics.  
Specifically, I don't think to_array() as an answer is workable unless we have 
some built-in chaining mechanism, pipes or similar.  Otherwise, you're 
effectively requiring anyone who wants to ensure they have an array (which will 
be a lot of people) to write

to_array(some_stuff_here());

All the time, which is considerably less ergonomic than 

some_stuff_here() |> to_array();

Especially if there's more than one operation involved.  

Side note: Should there instead be to_list() (which implies dropping keys and 
reindexing) and to_assoc() (which implies keeping keys and merging on key if 
appropriate)?  That seems much more self-documenting than to_array(true) or 
whatever.

>> 3. Feel free to borrow liberally, design-wise, from the above code.  There's 
>> a few more methods in there that could be of use, too.  Note, though, that 
>> all are designed to be used with a pipe(), so they mostly return a closure 
>> that has been manually partially applied with everything except the 
>> iterable, so you get a single-argument function, which is what a pipe() or 
>> compose() chain needs.
>> 
>> Third, speaking of pipe, I disagree with Tim that putting the callback first 
>> would be easier for pipe/partials.  If we ever get partials similar to 
>> previously implemented, then the argument order won't matter.  If we get 
>> pipes as I've previously proposed, then none of these functions are directly 
>> usable because they're multi-argument.
>> 
>> The alternative I've considered is somewhat inspired by Elixir (assuming I 
>> understand the little Elixir I've read), in which a function after a |> is 
>> automatically assumed to be partially applying everything but the first 
>> argument.  So $list |> map($callable) translates to map($list, $callable).  
>> I've not decided yet if that's a good way to avoid needing full partial 
>> application or a good way to make horribly confusing code.  But if that were 
>> to happen, it would only work if all of these functions took the iterable, 
>> the "object to be operated on", as their first argument.
>> 
>
> With Haskell, from which my functional programming experience comes, 
> it's the exact inverse: All functions are automatically partially 
> applied [2], thus in a pipe the "missing" parameter comes last, not 
> first as with Elixir.
>
> So I guess there is no right or wrong and it depends on the programming 
> language which variant feels more natural. I still prefer having the 
> callback first for the reasons I've outlined, but in the end I'm happy 
> as long as it's consistent.

Mm, yes, the root question is what kind of auto-partialing we are going to 
have.  Right now we have none, which is problematic for any sort of 
composition, which these iterable functions are going to want to have.  Whether 
we partial from the left or from the right will determine how we order 
parameters for functions we expect to be partialed.  

We should bear in mind that PHP has a couple of features that complicate 
matters; namely optional arguments, variadics, and named arguments.  The 
previous RFC that Joe wrote handled all of those as gracefully as I think is 
possible, but the complexity was apparently too high for too many folks.  He 
didn't seem confident that a less-complex approach was possible, though.

I know this is veering off topic, but one idea I had kicked around that Joe 
didn't like was a function call prefix to indicate that it was a partial rather 
than a direct call.  Something like %foo($a, $b, ?), to indicate that we were 
partially applying the function rather than calling it.  I don't know if that 
would actually work, especially if combined with some simplified set of 
capabilities for better auto-partialing.

These questions are not immediately part of the topic of this thread, but 
they're closely-related topics that should be considered when designing them.

--Larry Garfield

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

Reply via email to