> On 5 Apr 2019, at 08:54, Larry Garfield <la...@garfieldtech.com> wrote:
> 
> On Wed, Mar 13, 2019, at 10:22 PM, Larry Garfield wrote:
>> On Wed, Mar 13, 2019, at 6:30 PM, Rowan Collins wrote:
>>> On 13/03/2019 21:10, Dik Takken wrote:
> 
>> If I can summarize the responses so far, they seem to fall into one of 
>> two categories:
>> 
>> 1) Love the idea, but wouldn't short-closures be close enough?
>> 
>> 2) Love the idea, but hate the particular syntax proposed.
>> 
>> On the plus side, it seems almost everyone is on board in concept, so 
>> yay.  That of course just leaves the syntax bikeshedding, which is 
>> always the fun part.
> 
> Bumping this thread again.
> 
> Thinking on it further, I see two possible syntactic approaches, given that 
> short lambdas as currently written would not give us a viable comprehension 
> syntax.
> 
> 1) [foreach ($list as $x => $y) if (condition) yield expression]
> 
> That is, essentially the same syntax as the list would be if wrapped in a 
> function, but with a more compact way of writing it.  The above would be 
> effectively identical to:
> 
> $gen = function () {
> foreach ($list as $x => $y)
>   if ($condition)
>     yield expression;
> }();
> 
> 
> (But with auto-capture.)  I am personally not at all a fan of the extra 
> verbosity (foreach, parens, etc.) but it seems most respondents in the thread 
> want it for familiarity.
> 
> Advantages:
> 
> * Very compact.
> * Works for both arrays and traversables
> * Would play very nicely with the proposed spread operator for iterables 
> (https://wiki.php.net/rfc/spread_operator_for_array).
> 
> Disadvantages:
> 
> * New syntax
> * If you need to do multiple filter or map operations it gets potentially 
> ugly and unwieldy.
> * Not super extensible.
> * Doesn't have a natural way to enforce the types produced.  (Although one 
> could add it easily.)
> 
> This approach has the advantage of being compact and working for both arrays 
> and traversables, but is new syntax.
> 
> 2) Allow comprehensions to work only on traversable objects, which lets us 
> chain methods.  Specifically:
> 
> $new = $anyTraversable->filter(fn($x) => $x < 0);
> 
> Would return a new traversable that filters $anyTraversable, using a 
> callable.  It would effectively be identical to 
> 
> $new = new CallbackFilterIterator($anyTraversable, fn($x) => $x < 0);
> 
> Similarly:
> 
> $new = $anyTraversable->map(fn($x) => $x * 2);
> 
> Would produce a new traversable that lazily produces a function over the 
> items as they're returned.   Equivalent to:
> 
> $new = function () {
> foreach ($list as $x)
>     yield expression;
> }();
> 
> And both would also need to support a key/value as well, probably if the 
> callable takes 2 parameters then it's $key, $value, if just one parameter 
> then it's just $value.
> 
> This approach has a few advantages:
> 
> * It piggy-backs on existing traversable behavior; essentially, rather than 
> short-syntax for generators it's short syntax for wrapping a bunch of 
> iterator objects around each other.
> * More elaborate cases (multiple filters, multiple maps) become somewhat 
> nicer; you can easily call filter() or map() multiple times and it's still 
> entirely obvious what's going on.
> * Has a natural (if verbose) way to enforce types: filter(fn($x) => $x 
> instanceof Foo || throw new \TypeError);
> * Actually, since short-lambdas already would support return type 
> declaration, there's another alternative: filter(fn($x) : Foo => $x);  
> (Although you'd probably just fit that into a filter function you're using 
> for something else.)
> * next() is already a useful method that works for the an() case discussed in 
> the RFC, and it flows very naturally.  I don't see a nice equivalent of 
> all(), however.
> 
> But also some disadvantages:
> 
> * It only works for traversable objects, not arrays.  (Workaround: new 
> ArrayObject($arr).)
> * It is more verbose than the other syntax option.
> * Adding special-meaning methods to Traversable objects is weird, and I don't 
> think we've done that anywhere before.  I have no idea if there are engine 
> implications.
> * The short lambda RFC becomes effectively a prerequisite, as it's way too 
> verbose to do with an anon function as we have now.
> * My gut feeling is it would be slower as it would likely mean more function 
> calls internally, but I've zero data to back that up.
> 
> And before someone else mentions it, it also poses some interesting possible 
> extensions that are not all that relevant to the current target, but would 
> fit naturally:
> 
> * a ->limit(0, 3) method, that is functionally equivalent to \LimitIterator.
> * Potentially RegexIterator() could also become a regex() method, that's a 
> special case of filter()?
> * Languages like Rust have a method to "run out" the comprehension ( 
> .collect() in the case of Rust).  We could easily do the same to produce a 
> resultant array, similar to the spread operator.  (That said, that should in 
> no way detract from the spread operator proposal, which I also like on its 
> own merits.)
> * Possibly other stuff that slowly turns iterables into "collection objects" 
> (sort of).
> 
> 
> Discussion:
> 
> For me, the inability to work with arrays is the big problem with the second 
> approach.  I very very often am type declaring my returns and parameters as 
> `iterable`, which means I may have an array and not know it. Using approach 2 
> means I suddenly really really need to care which kind of iterable it is, 
> which defeats the purpose of `iterable`.  Calling methods on arrays, though, 
> I'm pretty sure is out of scope.
> 
> Frankly were it not for that limitation I'd say I favor the chained method 
> style, as while it is more verbose it is also more self-documenting.  Given 
> that limitation, I'm torn but would probably lean toward option 1.   And of 
> course there's the "methods that apply to all traversable objects" thing 
> which is its own can of worms I know nothing about.
> 
> (If someone has a suggestion for how to resolve that disadvantage, I'd love 
> to hear it.)
> 
> Those seem like the potential options.  Any further thoughts?  Or volunteers? 
> :-)
> 
> --Larry Garfield
> 
> -- 
> PHP Internals - PHP Runtime Development Mailing List
> To unsubscribe, visit: http://www.php.net/unsub.php
> 

(Sorry, sent from wrong address, sending again!)

Hi Larry,

I’ve mostly ignored this thread until now - I find a lot of the “shorter 
syntax” (i.e. the short closures RFC) to sound a lot like the arguments “I 
don’t like semicolons/it has to be ‘pretty'” that happen in other language 
communities.

But the first example you give here, I can see the logical approach - as you 
say, it’s a currently-valid foreach statement, wrapped in square brackets. 
Would it have to be a single line to parse, or could it be wrapped when the 
condition gets longer (yes I know it could just become a regular generator 
then, I’m just wondering about what happens when someone adds a new line in 
there (in a language that historically doesn’t care about newlines)

I like the second concept a lot too, but how would this cope with for example: 
a userland class implements iterator but *also* defines a `filter(callback 
$fn): self` method for the exact same purposes were discussing. How is that 
handled?


Cheers

Stephen



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

Reply via email to