Hi internals!
Currently PHP does not have an internal iteration API that supports
both arrays and Traversable objects. Because of that it is currently
not really possible to write functions (or language features) that
work on arbitrary traversables. That's probably also the reason why
function like array_sum()
currently work only on arrays and arrays
only.
So I'd really like to have such an API. One idea for an implementation
would be this:
-
Create a zend_create_iterator_from_zval() function, which returns a
zend_object_iterator -
For arrays and plain objects this would create a thin wrapper
around the zend_hash_* iteration functions -
For objects defining get_iterator this would just return the
object_iterator that get_iterator returned -
Usage:
zend_object_iterator *iter = zend_create_iterator_from_zval(zval); if (iter->rewind) iter->rewind(iter); while (iter->valid(iter)) { zval **value; iter->get_current_data(iter, &value); // do something iter->move_forward(iter); } iter->dtor(iter);
I like this approach because it reuses the existing
zend_object_iterator API. But I see that this is rather an abuse than
a use :)
Thoughts?
Nikita
Love the idea. +1
Hi internals!
Currently PHP does not have an internal iteration API that supports
both arrays and Traversable objects. Because of that it is currently
not really possible to write functions (or language features) that
work on arbitrary traversables. That's probably also the reason why
function likearray_sum()
currently work only on arrays and arrays
only.So I'd really like to have such an API. One idea for an implementation
would be this:
Create a zend_create_iterator_from_zval() function, which returns a
zend_object_iteratorFor arrays and plain objects this would create a thin wrapper
around the zend_hash_* iteration functionsFor objects defining get_iterator this would just return the
object_iterator that get_iterator returnedUsage:
zend_object_iterator *iter = zend_create_iterator_from_zval(zval); if (iter->rewind) iter->rewind(iter); while (iter->valid(iter)) { zval **value; iter->get_current_data(iter, &value); // do something iter->move_forward(iter); } iter->dtor(iter);
I like this approach because it reuses the existing
zend_object_iterator API. But I see that this is rather an abuse than
a use :)Thoughts?
Nikita
Hi internals!
Currently PHP does not have an internal iteration API that supports
both arrays and Traversable objects.
Yes. in fact doing that is long overdue.
Because of that it is currently
not really possible to write functions (or language features) that
work on arbitrary traversables. That's probably also the reason why
function likearray_sum()
currently work only on arrays and arrays
only.
One thing to keep in mind when doing this is to think about consistency.
Right there's quite a distinction. Things either take an array or a
Traversable object. We should think about not creating a mess when some
functions, especially ones called array_foo() allow Traversable while
others won't. So we might need the same infrastructure in regards to
ArrayAccess to help this a bit.
Just picking some random example:
* `array_map()` - This can be implemented using that infrastructure.
(While some might think about returning an Traversable object if
a Traversable object got put in, maybe a FilterIterator but
let's ignore that)
* `array_filter()` - This is almost the same as `array_map()` but we
can't implement it using Traversable, as `current()` (for good
reasons) returns no reference.
Another "fun fact" might be that Traversable doesn't require keys to be
unique. This can have strange results for things like array_map()
.
Just to be clear I fully support it, but this is an area which clearly
requires some more global design. Maybe even going through all functions
processing arrays and categorizing them to see the impact.
Ah, and maybe completely unrelated to the things above but not
to forget: When implementing this the code becomes more complex
as exceptions thrown in `key()`, `current()`, `rewind()` have to be
caught. With "classic" zend_hash iteration those operations will
succeed if there was a next element pointer. Maybe that can be
handled in a generic infrastructure, so we have less errors
later.
johannes
Just picking some random example:
* `array_map()` - This can be implemented using that infrastructure. (While some might think about returning an Traversable object if a Traversable object got put in, maybe a FilterIterator but let's ignore that) * `array_filter()` - This is almost the same as `array_map()` but we can't implement it using Traversable, as `current()` (for good reasons) returns no reference.
I meant array_walk()
. array_filter()
shouldn't be an issue either.
That happens, when you shorten a list of examples ...
johannes
Johannes,
One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning that array_sum()
, etc should all take arrays only.
Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.
Just my $0.02 anyway...
Anthony
On Wed, Jul 11, 2012 at 5:39 PM, Anthony Ferrara ircmaxell@gmail.comwrote:
Johannes,
One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning thatarray_sum()
, etc should all take arrays only.Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.Just my $0.02 anyway...
Sounds like everyone agrees the API is useful, just question marks over
which existing methods should profit by it.
To add my $0.02, it'd be nice to work in a zend_parse_parameters() type for
this. Keep extension code clean and ensures any temporary iterators get
destructed.
-Sara
Hi!
To add my $0.02, it'd be nice to work in a zend_parse_parameters() type for
this. Keep extension code clean and ensures any temporary iterators get
destructed.
Definitely, that'd be a great benefit. Just return zend_iterator * which
you can use to get current/next zvals and converting functions to work
with any Traversable will be a breeze.
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
--e89a8f235453d7a80104c4975c55
On Wed, Jul 11, 2012 at 5:39 PM, Anthony Ferrara ircmaxell@gmail.comwrote:One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning thatarray_sum()
, etc should all take arrays only.Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.Just my $0.02 anyway...
Sounds like everyone agrees the API is useful, just question marks over
which existing methods should profit by it.To add my $0.02, it'd be nice to work in a zend_parse_parameters() type for
this. Keep extension code clean and ensures any temporary iterators get
destructed.
Another note: if this comes to fruition, since arrays and traversable objects
would in many cases be interchangeable, it would be nice to have a type-hint for
"array-like" behavior:
function doSomething (ArrayLike $options) { ... }
A better name could likely be used, but you get the idea.
--
Matthew Weier O'Phinney
Project Lead | matthew@zend.com
Zend Framework | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
--e89a8f235453d7a80104c4975c55
On Wed, Jul 11, 2012 at 5:39 PM, Anthony Ferrara ircmaxell@gmail.comwrote:One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning thatarray_sum()
, etc should all take arrays only.Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.Just my $0.02 anyway...
Sounds like everyone agrees the API is useful, just question marks over
which existing methods should profit by it.To add my $0.02, it'd be nice to work in a zend_parse_parameters() type for
this. Keep extension code clean and ensures any temporary iterators get
destructed.
Another note: if this comes to fruition, since arrays and traversable objects
would in many cases be interchangeable, it would be nice to have a type-hint for
"array-like" behavior:function doSomething (ArrayLike $options) { ... }
A better name could likely be used, but you get the idea.
What about extending the array typehint include ArrayAccess, and extend
the Traversable typehint to include arrays?
--e89a8f235453d7a80104c4975c55
One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning thatarray_sum()
, etc should all take arrays only.Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.Just my $0.02 anyway...
Sounds like everyone agrees the API is useful, just question marks over
which existing methods should profit by it.To add my $0.02, it'd be nice to work in a zend_parse_parameters() type for
this. Keep extension code clean and ensures any temporary iterators get
destructed.
Another note: if this comes to fruition, since arrays and traversable objects
would in many cases be interchangeable, it would be nice to have a type-hint for
"array-like" behavior:function doSomething (ArrayLike $options) { ... }
A better name could likely be used, but you get the idea.
What about extending the array typehint include ArrayAccess, and extend
the Traversable typehint to include arrays?
This would work for Traversable, as that interface does not define any methods,
and the only use case would be for iteration. This would answer most use
cases/issues I've had (need either an array or Traversable in order to iterate).
For arrays, however, it's a bit harder, as the reason for typehinting on array
may not be solely access of members, but also iteration. As such, extending
the array typehint to include ArrayAccess could lead to issues when the object
implementing ArrayAccess does not also implement Traversable. I don't think this
would work at all semantically.
--
Matthew Weier O'Phinney
Project Lead | matthew@zend.com
Zend Framework | http://framework.zend.com/
PGP key: http://framework.zend.com/zf-matthew-pgp-key.asc
On Fri, Jul 13, 2012 at 3:55 PM, Matthew Weier O'Phinney
weierophinney@php.net wrote:
--e89a8f235453d7a80104c4975c55
One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning thatarray_sum()
, etc should all take arrays only.Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.Just my $0.02 anyway...
Sounds like everyone agrees the API is useful, just question marks over
which existing methods should profit by it.To add my $0.02, it'd be nice to work in a zend_parse_parameters() type for
this. Keep extension code clean and ensures any temporary iterators get
destructed.
Another note: if this comes to fruition, since arrays and traversable objects
would in many cases be interchangeable, it would be nice to have a type-hint for
"array-like" behavior:function doSomething (ArrayLike $options) { ... }
A better name could likely be used, but you get the idea.
What about extending the array typehint include ArrayAccess, and extend
the Traversable typehint to include arrays?This would work for Traversable, as that interface does not define any methods,
and the only use case would be for iteration. This would answer most use
cases/issues I've had (need either an array or Traversable in order to iterate).For arrays, however, it's a bit harder, as the reason for typehinting on array
may not be solely access of members, but also iteration. As such, extending
the array typehint to include ArrayAccess could lead to issues when the object
implementing ArrayAccess does not also implement Traversable. I don't think this
would work at all semantically.
Yep, today we have :
- arrays
- Object implementing ArrayAccess
- Objects implementing Traversable (Iterator)
- array_****() functions not working with ArrayAccess' objects
- Iterator API not working with arrays (ArrayIterator needed)
- ... I forget things
There sure is something to do with that to make a consistent API.
So what would be the right answer to make all those ideas join in a
sane way everybody agree with ?
I got no clue, but I think we should answer this Q for 5.5
Cheers,
Julien.P
Hi!
Yep, today we have :
- arrays
- Object implementing ArrayAccess
- Objects implementing Traversable (Iterator)
- array_****() functions not working with ArrayAccess' objects
- Iterator API not working with arrays (ArrayIterator needed)
- ... I forget things
There sure is something to do with that to make a consistent API.
Definitely, having consistent generic iteration API would be great, and
pretty much the only reason many iterative functions don't suport
traversables is that there's no such API.
As for ArrayAccess, it's more complicated. For example, if you have
array_shift()
I'm not sure ArrayAccess is enough to implement it. To
find out "first" element you'd need Traversable, but even that won't
allow you to "shift" array elements. So for some array_* functions only
thing that you can rely on is an actual array...
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Hi!
Yep, today we have :
- arrays
- Object implementing ArrayAccess
- Objects implementing Traversable (Iterator)
- array_****() functions not working with ArrayAccess' objects
- Iterator API not working with arrays (ArrayIterator needed)
- ... I forget things
There sure is something to do with that to make a consistent API.
Definitely, having consistent generic iteration API would be great, and
pretty much the only reason many iterative functions don't suport
traversables is that there's no such API.As for ArrayAccess, it's more complicated. For example, if you have
array_shift()
I'm not sure ArrayAccess is enough to implement it. To
find out "first" element you'd need Traversable, but even that won't
allow you to "shift" array elements. So for some array_* functions only
thing that you can rely on is an actual array...
So, I've not been inside the engine so in practice this may make no
sense at all, but what about looking at it the other way around? Vis,
instead of making objects more and more like arrays, make arrays an
object that happens to implement ArrayAccess, Iterator, and whatever
else, with an implementation that matches its current behavior? I mean,
it's not like they're actual language arrays now, they're hash maps with
a convenient syntax.
Then the array_* functions that need to be able to iterate can
internally type hint Traversable (and thus take any Traversable, of
which a hashmap would be one), those that need to []-access can type
hint ArrayAccess, and those that need both can hint, hum,
TraversableArrayAccess or something (that extends both of those).
Effectively, turn array() and [...] into syntactic sugar on top of a
"HashMap implements ArrayAccess, Traversable" object, much as anonymous
functions are, as I understand it, syntactic sugar on top of an object
that has an __invoke() method.
Combined with the generator proposal and recent improvements to filters,
that could lead to some seriously slick potential.
$hash_map_of_type_MyIteratableClass = array_map($some_closure,
$generator_of_some_sort, 'MyIterableClass');
Would that alleviate the "oh god it's two things" problem without
causing too many other issues?
--Larry Garfield
Hi!
So, I've not been inside the engine so in practice this may make no
sense at all, but what about looking at it the other way around? Vis,
instead of making objects more and more like arrays, make arrays an
object that happens to implement ArrayAccess, Iterator, and whatever
That'd be very nice idea if we were implementing new PHP. I think the
fact that arrays are not objects in PHP is a source of many problems.
However, right now it probably won't be possible to do it without
breaking BC in many places dealing with array copy/assignment/passing
semantics.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
Hi!
So, I've not been inside the engine so in practice this may make no
sense at all, but what about looking at it the other way around? Vis,
instead of making objects more and more like arrays, make arrays an
object that happens to implement ArrayAccess, Iterator, and whatever
That'd be very nice idea if we were implementing new PHP. I think the
fact that arrays are not objects in PHP is a source of many problems.
However, right now it probably won't be possible to do it without
breaking BC in many places dealing with array copy/assignment/passing
semantics.
Hm, valid point. Is there no way that we could setup one object to pass
"the old way"? I suppose that would result in just as many special case
exceptions...
Perhaps that's a change that could be considered for whatever version
does get called PHP 6? PHP 5 changed the passing semantics for objects
and we survived that, and it was a big boon to the language. Perhaps
that could be rolled into bigger changes later? (There's a PHP 6 thread
right now I've not looked at yet...)
--Larry Garfield
Hi!
So, I've not been inside the engine so in practice this may make no
sense at all, but what about looking at it the other way around? Vis,
instead of making objects more and more like arrays, make arrays an
object that happens to implement ArrayAccess, Iterator, and whateverThat'd be very nice idea if we were implementing new PHP. I think the
fact that arrays are not objects in PHP is a source of many problems.
However, right now it probably won't be possible to do it without
breaking BC in many places dealing with array copy/assignment/passing
semantics.
I'd then say : let's keep that idea somewhere to implement it in a new
major PHP version.
Anyway, there will come a time where we will be developing for PHP6
(or PHP next Major if you prefer), and I think we should use
this gap to break BC and add new cool ideas like this one, we seem all
to agree with.
Julien.P
Hi!
So, I've not been inside the engine so in practice this may make no
sense at all, but what about looking at it the other way around? Vis,
instead of making objects more and more like arrays, make arrays an
object that happens to implement ArrayAccess, Iterator, and whatever
That'd be very nice idea if we were implementing new PHP. I think the
fact that arrays are not objects in PHP is a source of many problems.
However, right now it probably won't be possible to do it without
breaking BC in many places dealing with array copy/assignment/passing
semantics.
I'd then say : let's keep that idea somewhere to implement it in a new
major PHP version.
Anyway, there will come a time where we will be developing for PHP6
(or PHP next Major if you prefer), and I think we should use
this gap to break BC and add new cool ideas like this one, we seem all
to agree with.Julien.P
Agreed. We survived Objects becoming, er, Objects in PHP 5.0. Arrays
changing would be a major version change, but if the benefits are
enough, we could survive that, too.
--Larry Garfield
On Sat, Jul 14, 2012 at 2:35 AM, Stas Malyshev smalyshev@sugarcrm.com
wrote:Hi!
So, I've not been inside the engine so in practice this may make no
sense at all, but what about looking at it the other way around? Vis,
instead of making objects more and more like arrays, make arrays an
object that happens to implement ArrayAccess, Iterator, and whateverThat'd be very nice idea if we were implementing new PHP. I think the
fact that arrays are not objects in PHP is a source of many problems.
However, right now it probably won't be possible to do it without
breaking BC in many places dealing with array copy/assignment/passing
semantics.I'd then say : let's keep that idea somewhere to implement it in a new
major PHP version.
Anyway, there will come a time where we will be developing for PHP6
(or PHP next Major if you prefer), and I think we should use
this gap to break BC and add new cool ideas like this one, we seem all
to agree with.Julien.P
Agreed. We survived Objects becoming, er, Objects in PHP 5.0. Arrays
changing would be a major version change, but if the benefits are enough, we
could survive that, too.
+1 great notice.
We had a huge step between PHP4 and PHP5. It's done now (hence it
wasn't that easy to achieve).
So it wont be frightening to see another gap between PHP5 and
PHP-NEXT-MAJOR , especially if it's lower than 4-to-5 was.
Julien.P
What about extending the array typehint include ArrayAccess, and extend
the Traversable typehint to include arrays?
For that all (internal) consumers of Traversable have to work with
arrays. i.e. <?php new IteratorIterator([1,2,3,4,5]); ?>. This could be
in the scope of the internal iteration but should not be forgotten.
johannes
Johannes,
One thing to keep in mind when doing this is to think about consistency.
I think that's a huge point not to be taken lightly. For that reason, I
think that this API should not be used for any of the array_* functions.
Meaning thatarray_sum()
, etc should all take arrays only.Then, other non-array functions (such as str_replace, etc) can be modified
to use this new method.
Not sure that's more consistent then.
For having a quick guess on how many functions might be affected,
roughly, I did a simple grep:
$ grep -cE 'zend_parse_parameters(_ex)?[^"]"[^"][aAhH][^"]"' \
ext//*.c Zend/zend_builtin_functions.c | grep -v :0
ext/curl/interface.c:1
ext/date/php_date.c:2
ext/dom/xpath.c:1
ext/filter/filter.c:1
ext/gd/gd.c:5
ext/iconv/iconv.c:1
ext/imap/php_imap.c:2
ext/ldap/ldap.c:2
ext/mysqli/mysqli_embedded.c:1
ext/mysqli/mysqli_nonapi.c:1
ext/odbc/php_odbc.c:1
ext/openssl/openssl.c:12
ext/pcntl/pcntl.c:4
ext/pcre/php_pcre.c:1
ext/pdo_pgsql/pgsql_driver.c:1
ext/pdo/pdo_dbh.c:2
ext/pdo/pdo_stmt.c:2
ext/pgsql/pgsql.c:11
ext/phar/phar_object.c:2
ext/reflection/php_reflection.c:3
ext/soap/soap.c:3
ext/sockets/sockets.c:1
ext/spl/spl_directory.c:1
ext/spl/spl_fixedarray.c:1
ext/spl/spl_iterators.c:1
ext/standard/array.c:43
ext/standard/basic_functions.c:4
ext/standard/file.c:1
ext/standard/proc_open.c:1
ext/standard/streamsfuncs.c:5
ext/sysvmsg/sysvmsg.c:1
ext/xmlrpc/xmlrpc-epi-php.c:4
ext/xsl/xsltprocessor.c:2
ext/zip/php_zip.c:2
In total 126 matches. Now somebody has to go quickly over them ;-)
johannes
Hi!
One thing to keep in mind when doing this is to think about consistency.
Right there's quite a distinction. Things either take an array or a
Traversable object. We should think about not creating a mess when some
functions, especially ones called array_foo() allow Traversable while
others won't. So we might need the same infrastructure in regards to
ArrayAccess to help this a bit.
We could create duplicates for functions that can accept Traversable and
give them neutrally sounding names. Though I don't see the reason why we
should ban something like array_map from accepting Traversable just
because some of the array_* functions don't - the question then would be
if there would be iterator_map() that accepts any Traversable why should
separate array_map exist at all?
Something like preg_grep should just accept any Traversable though.
Ah, and maybe completely unrelated to the things above but not to forget: When implementing this the code becomes more complex as exceptions thrown in `key()`, `current()`, `rewind()` have to be caught. With "classic" zend_hash iteration those operations will
Can we have the wrapper handle it? I.e. if the function calls
iterator_next(iterator), and it's a custom one which throws an exception
in next()
then iterator_next() should take care of it and return that
there's no next element. Though I really don't see why such functions
should be allowed to throw exceptions - this seems overcomplicated to me
as then you can have exceptions thrown by most innocent operations like
str_replace, and it'd be very hard to handle such exceptions.
--
Stanislav Malyshev, Software Architect
SugarCRM: http://www.sugarcrm.com/
(408)454-6900 ext. 227
On Thu, Jul 12, 2012 at 1:49 AM, Johannes Schlüter
johannes@schlueters.de wrote:
One thing to keep in mind when doing this is to think about consistency.
Right there's quite a distinction. Things either take an array or a
Traversable object. We should think about not creating a mess when some
functions, especially ones called array_foo() allow Traversable while
others won't. So we might need the same infrastructure in regards to
ArrayAccess to help this a bit.
Yes, I agree that overloading the array_* functions would be a bit
strange (but not that strange either). My personally favorite
approach would be to create new functions like sum(), product() and
reduce(), which would be array_sum()
, array_product()
and
array_reduce()
equivalents, but accepting anything iterable as input.
The old array_* functions would obviously stay (maybe as aliases,
maybe complete separate).
For the array_map and array_filter I would do the same (i.e. create
map() and filter()), which would return an array when passed an array,
but return a (lazy) iterator when passed an iterator.
* `array_filter()` - This is almost the same as `array_map()` but we can't implement it using Traversable, as `current()` (for good reasons) returns no reference.
I'm not sure how array_filter()
and references relate (maybe you meant
array_walk?), but in any case: get_iterator style object iterator
can support references. The function has a by_ref flag. But
currently userland iterators can't make use of this. This limitation
can be easily removed though, as far as I know. It is just a leftover
from previously existing technical limitations regarding interfaces
and references.
Another "fun fact" might be that Traversable doesn't require keys to be
unique. This can have strange results for things likearray_map()
.
Yeah, that's one of the reasons why I would prefer map() to return an
iterator if an iterator is passed in. Another reason is that iterators
can be infinite, which obviously doesn't play nice when returning an
array. (And even if they aren't infinite, they may be large).
Ah, and maybe completely unrelated to the things above but not to forget: When implementing this the code becomes more complex as exceptions thrown in `key()`, `current()`, `rewind()` have to be caught. With "classic" zend_hash iteration those operations will succeed if there was a next element pointer. Maybe that can be handled in a generic infrastructure, so we have less errors later.
Yeah, you're right about that. It wouldn't be nice if one had to check
for errors after every single call :/
Nikita
-----Original Message-----
From: Nikita Popov [mailto:nikita.ppv@gmail.com]
Sent: 11 July 2012 23:17
To: PHP internals
Subject: [PHP-DEV] Internal iteration APIHi internals!
Currently PHP does not have an internal iteration API that
supports both arrays and Traversable objects. Because of that
it is currently not really possible to write functions (or
language features) that work on arbitrary traversables.
That's probably also the reason why function likearray_sum()
currently work only on arrays and arrays only.So I'd really like to have such an API. One idea for an
implementation would be this:
Create a zend_create_iterator_from_zval() function, which
returns a zend_object_iteratorFor arrays and plain objects this would create a thin
wrapper around the zend_hash_* iteration functionsFor objects defining get_iterator this would just return
the object_iterator that get_iterator returnedUsage:
zend_object_iterator *iter =
zend_create_iterator_from_zval(zval);
if (iter->rewind) iter->rewind(iter);
while (iter->valid(iter)) {
zval **value;
iter->get_current_data(iter, &value);
// do something
iter->move_forward(iter);
}
iter->dtor(iter);I like this approach because it reuses the existing
zend_object_iterator API. But I see that this is rather an
abuse than a use :)Thoughts?
Nikita
I presume this would work with list() too?
function *f()
{
yield 'a';
yield 'b';
}
list($a, $b) = f();
Jared