

PHP gets Generators - TazeTSchnitzel
https://wiki.php.net/rfc/generators#vote

======
antirez
PHP is the perfect showcase of how good languages are not sum of features at
all. I don't think you can fix PHP just adding stuff that it lacks. It can be
improved in this way as, at least, it is possible for a good programmer to
pick a subset of features that makes the language less a pain to use, but I
wish PHP could start changing in a "let's redesign it" way.

Disclaimer: I'm a big fan of PHP, not because I like the language, but because
I like the _pragmatic_ approach.

~~~
wvenable
> I wish PHP could start changing in a "let's redesign it" way.

I think that's foolish. Redesigning a language is making a new language -- and
there are lots of other new languages to choose from. What features of PHP
would you want to keep that aren't possible in other languages?

Radically changing the language also leads to heavily splitting the community
like with PHP4/PHP5 or Perl5/Perl6 or Python2/Python3 and nobody wants that.
Backwards compatibility doesn't have to be fully maintained but it does have
to be respected.

The biggest issue right now with PHP is that there isn't a clear direction on
where to go. Nobody wants to "fix" some of the old problems because the gains
don't seem to outweigh the costs.

~~~
jrockway
There is room for a language that generates HTML by default, compiles every
library anyone could ever need into the core, and puts it all in the default
namespace. All you need to make that socially acceptable is something like
GHC's -XNoImplicitPrelude option. By default, you get what might be considered
to be a big mess. But when someone with a clue shows up to turn your prototype
into something maintainable, they can turn on "expert mode" and program
normally again.

That's what PHP is missing: expert mode. As it stands now it's very difficult
to turn off all the traps, so experienced programmers are forced to switch to
other languages. Compare this to Perl, where it has silly behavior by default,
but you can turn it off with "use strict" at the top of the file.

~~~
wvenable
My PHP code looks pretty much identical to my code in other languages.
Everything is in namespaces and classes. There is abstractions for database
access and other common operations. I don't think PHP needs an expert mode; it
just needs to continue to be a better language.

However, I agree, all languages should have the ability to turn off the
backwards compatibility crud with some kind of statement. In PHP, it would be
great to do that at the namespace level -- allowing different libraries to
operate at different levels of support/strictness in the same project.

~~~
encoderer
I've used PHP a lot, work at a name-brand startup that uses a fair bit of PHP
in the stack, and have an OSS PHP project I'm proud of
(<https://github.com/shaneharter/PHP-Daemon>). That is to say, I'm not a PHP
hater. I see many flaws in the language, many many actually, but I'm not a
hater.

But I wonder if maybe you're blind to the ways your PHP is different. Dozens
of modules and hundreds of functions come directly with the language core. And
very very esoteric stuff. Why does my _programming language_ have a function
to tell me specifically what day Easter is? Whaa? Why do I have to recompile
my _programming language_ to install new libraries? (PCNTL, for example).

If PHP is a salad bar, Python (for example) is prix fixe.

~~~
rll
You don't. Watch:

    
    
      wget http://www.php.net/get/php-5.3.16.tar.gz/from/us2.php.net/mirror -O php-5.3.16.tar.gz
      tar zxvf php-5.3.16.tar.gz
      cd php-5.3.16/ext/pcntl
      phpize
      ./configure
      make
      sudo make install
    

At this point you should see a pcntl.so in your extension_dir. Add an
extension=pcntl.so to your php.ini file and you are good to go.

Of course with non-bundled extensions from pecl or github you can skip the
tarball step.

~~~
encoderer
That's no less omplicated than downloading the PHP source and recompiling with
the --with-pcntl flag. Now compare that, just for your own wonderment, to how
it works in other modern languages..

pip install package_name or gem install package_name

not to mention, with bundler and virtualenv you can easily, easily keep
multiple versions of different packages for different projects without any
juggling and hacking your way through.

~~~
soulclap
Easily? I remember having quite some difficulties getting gems with binaries
to work on Windows (meaning: lots of wrestling before I even got the MySql
driver working). Might be different now though but I have never experienced
anything like that when dealing with PHP extensions or Composer packages.

------
SeoxyS
This feature seems seriously contrived and they could have tried to learn a
thing or two from ruby here.

The idea that the code flow is now no longer linear, and that the programmer
now has to understand perfectly the odd rules by which the execution of code
now just jumps around… boggles the mind.

A closure-based approach would have been much more semantically sane; if a
little syntactically ugly. But then again, how hard would it have been to add
a nicer closure syntax to PHP, instead of creating this mess of a feature?

Take this:

    
    
        def each_line_in_file
          f = File.open('hello.txt', 'r')
          while line = f.gets
            yield line
          end
          f.close
        end
        
        each_line_in_file do |l|
          puts l
        end
    

This is a real-world Ruby example of the feature. The semantics here are
clear, each_line is a higher-order function which takes a closure as an
arguments, and calls it (yields to it) for each line in the file. Ruby allows
the programmer to use the yield keyword to avoid having to declare the closure
as an argument (`def each_line(&block)`).

The same can even be implemented in PHP as it stands today, although the
syntax is quite unsightly:

    
    
        function each_line_in_file($callback) {
            $f = fopen('hello.txt', 'r');
            while (null !== ($line = fgets($f))) {
                $callback($line);
            }
            fclose(f);
        }
        
        each_line_in_file(function ($l) {
          echo $l."\n";
        });

~~~
masklinn
> This feature seems seriously contrived and they could have tried to learn a
> thing or two from ruby here.

That's way too late, PHP is an imperatively styled and statements-based
language, it uses iterables and external iteration, not internal. That's what
the language is.

> The semantics here are clear

There's nothing unclear about generator semantics, regardless of your refusal
to understand them.

> The same can even be implemented in PHP as it stands today

Not quite, PHP doesn't have non-local returns, so you soon get into annoying
issues of not being able to break away from an iteration (or of having an
utterly terrible API to do so, as in [NSArray enumerateObjectsUsingBlock:]

> Ruby allows the programmer to use the yield keyword to avoid having to
> declare the closure as an argument (`def each_line(&block)`).

So you dislike one magic but you like an other for pretty much no reason at
all beyond knowing one and not the other? Consistency not your style?

~~~
SeoxyS
> So you dislike one magic but you like an other for pretty much no reason at
> all beyond knowing one and not the other? Consistency not your style?

The thing I love about Ruby's magic is that it's not really magic, it's an
abstraction of solid fundamentals.

There's nothing really magic about ruby's yield, because it's a shortcut for
explicitly declaring a closure and calling it. These are real language
concepts that are well understood. PHP's yield, however, is a completely new
construct. The jumping around does not follow well-understood rules (like
calling a function). In ruby's case, that's what it does. It simply executes
the function, and calls a closure.

In PHP now when you define a function, and then call that function; it may or
may not execute the code inside that function, depending on whether later on
in the function there's a `yield` keyword. That's _insane_!

Say you can see this in your code editor:

    
    
        public function hello() {
            // a lot of important code
            $myvar = $this->other_cool_function();
            // more code
        }
        public function other_cool_function() {
            echo "Hello, world!\n";
        . . .
    

You're looking at line 3, the line which calls the other cool function.
Looking at this code you'd expect that to print "Hello, world!" first, and do
some other stuff in the rest of the function. But wait! You didn't notice, 30
lines down the other function, there's a yield keyword, which means that the
entire function doesn't ever execute until you start iterating over it…

~~~
jasonlotito
Not familiar enough with Ruby, but wouldn't your Ruby sample have this same
problem?

~~~
anonymoushn
No, the ruby "generator" is actually a function that takes a function and
calls it with each line from the file, like this:

    
    
      >>> the_list = [1,2,4,7]
      >>> def ruby_generator(f): 
      ...   for x in the_list: 
      ...     f(x) 
      ...     
      >>> def print_it(x): 
      ...   print x 
      ...   
      >>> ruby_generator(print_it)
      1
      2
      4
      7

~~~
jasonlotito
That's not different than what PHP does now.

I think you might be confused. I'm referring to the GP post that has the
example with the yield call in it, the ruby example.

~~~
anonymoushn
> That's not different than what PHP does now.

You could write code like that in PHP, and the GP post with the ruby example
does so, but PHP generators do not work like that.

> I think you might be confused. I'm referring to the GP post that has the
> example with the yield call in it, the ruby example.

The Ruby example doesn't have the problem you mentioned because Ruby doesn't
have generators at all. In Python and PHP, yield means "Suspend execution of
this function and return a value to the caller, who can later call this
function again to resume its execution." In Ruby, yield means "Call the block
that was passed to this function with the values after yield," or exactly what
I've written in Python without using the yield keyword. Ruby's "yeild line" is
Python's "f(x)".

Edited: I think I understand what you mean after reading once more. If you
mistook the function containing yield in Ruby for a function that does not
contain yield you would still have a problem, but not the same problem you
would have in PHP or Python. It would raise an exception, while PHP and
Python's would just return an iterable.

    
    
      def each_line_in_file 
      ..   yield 1 
      .. end
      => nil
      each_line_in_file
      (eval):2: (eval):2:in `each_line_in_file': no block given (LocalJumpError)
          from (eval):3

~~~
jasonlotito
> I think I understand what you mean after reading once more.

Thanks for taking the time to understand. My apologies for not being clear
from the outset.

------
mappu
I'm not quite caught up on the mailing list - but it looks like the last-
minute point about traversing an already-exhausted generator (see [1]) has
held.

That's really the most important thing for developers to notice - whether you
use the feature often or not, you must be aware that foreach() might _throw_ ,
not just emit an E_WARNING.

____________________

1\. <https://wiki.php.net/rfc/generators#rewinding_a_generator>

~~~
TazeTSchnitzel
I guess you'll be upset then that I, personally, was for it using Exceptions,
because Generators are Iterators.

~~~
mappu
It's a sensible choice and i understand the reason for it being this way. It
does seem like it's exposing an implementation detail, though, and out of the
rest of the core language syntax, nothing else throws an exception.

I'm happy to see the feature, but this is one of those quirks that, in another
five years, will end up in the next "A Fractal Of Bad Design" blog post.

------
mgkimsal
Interesting - only one vote against (final numbers were 24-1).

~~~
TazeTSchnitzel
I find it amusing that, on the PHP internals mailing list, RFCs will be
heavily "debated" (i.e., argued) about, and you'd think from reading it
everyone hates something.

Then it comes to vote and >90% vote for it, usually :)

~~~
dpcx
Everything should be debated. It ensures that you get the best possible
product. We may agree on something, but if no opposing views are given, how do
we know it's the best?

~~~
TazeTSchnitzel
I'm not opposing debate.

You'd understand what I was on about if you read the PHP internals mailing
list - the same points are argued to death; instead of a good discussion, it
becomes a huge argument.

------
agumonkey
I used to say that PHP tried to be Java. Closures, traits and now generators,
linguistically it's getting ahead.

~~~
TazeTSchnitzel
PHP is still very Java-like in some ways though. Well... PHP likes to pretend
it's a dynamic language (Closures, for instance), but at its core it's a very
strange dynamic/static hybrid, utilising a single-pass compiler and with type
checking done with syntax definitions(!)

Which is why this doesn't work: (actual intended production code)

    
    
      self::$views[$path]();
    

Why? Because the syntax doesn't accomodate calling a closure that is an item
in an array, which is a static member of a class.

But this works:

    
    
      $x = self::$views[$path];
      $x();

~~~
masklinn
> PHP likes to pretend it's a dynamic language (Closures, for instance),

PHP is a dynamic(ally typed) language, and closures have nothing to do with
static/dynamic.

> at its core it's a very strange dynamic/static hybrid

No, at its core it's completely dynamically typed, it got static features when
it decided to base its OO on Java's.

> Which is why this doesn't work:

This doesn't work because PHP uses a shitty ad-hoc parser for a shitty ad-hoc
syntax, much like closures it's completely orthogonal to the language being
dynamic or static.

Also, this piece of code doesn't involve any closure. It looks like you don't
know what a closure is.

~~~
TazeTSchnitzel
>Also, this piece of code doesn't involve any closure. It looks like you don't
know what a closure is.

Yes it does, it executes what _PHP_ calls a "Closure":
<http://php.net/manual/en/class.closure.php>

~~~
encoderer
Actually, not really.

Your code breaks on any PHP "callable"
<http://php.net/manual/en/language.types.callable.php> of which Closures are
just one.

~~~
TazeTSchnitzel
I'm well aware of that.

Although it seems PHP tried to treat it as a string callable IIRC.

------
krob
I think probably the biggest hurdle for a lot of devs is really a fix on
naming conventions and argument order. I think this could be accomplished with
a large aliased namespace. maybe something like

use \stdlib as _;

_\array_walk(<array>,<callback>) _\array_map(<array>,<callback>)
_\array_reduce(<array>,<callback>) ...

------
Kilimanjaro

        the/worst/php/travesti/is/namespaces

~~~
wvenable
Backslash.

And honestly, it's really not bad.

~~~
soulclap
Not getting the problem either, working with PHP namespaces daily.

