PHP 10.0 Blog

What if…

Ruby-like iterators in PHP

Posted by Stas on January 27, 2010

I’ve started playing with Ruby recently, and one of the things that got my attention in Ruby were iterators. They are different inside from regular loops but work in a similar way, and looks like people (at least ones that write tutorials and code examples ;) ) like to use them. For example, you can have:

arr = {"one" => 1, "two" => 2, "three" => 3}
arr.each do |key, val|
print "#{key}: is #{val}\n"
end

which iterates over a Ruby hash and prints:

three: is 3
two: is 2
one: is 1

So it got me thinking – suppose I wanted to do something like this in PHP (suppose I don’t like regular loop-y iterators for some weird reason). Naturally, I wouldn’t get it in the same concise form as Ruby does, since I can’t change the syntax. But I could get the essence. Let’s try it. First, the main iterator:

class RubyIterator {
  protected $_body;

  public function __construct($body) {
    if(!is_callable($body)) {
      throw new Exception("Iterator body should be a callable");
    }
    $this->_body = $body;
  }
  public function yield()
  {
    $args = func_get_args();
    call_user_func_array($this->_body, $args);
  }
}

Next, less try to make some class that uses it:

class RubyArray {
    protected $_arr;
    public function __construct(array $a)
    {
        $this->_arr $a;
    }

    public function each($body) {
        $iter = new RubyIterator($body);
        foreach($this->_arr as $k => $v) {
            $iter->yield($k$v);
        }
    }
}

and then:

/*
arr = {"one" => 1, "two" => 2, "three" => 3}
arr.each do |key, val|
    print "#{key}: is #{val}\n"
end
*/
    
$arr = new RubyArray(array("one" => 1"two" => 2"three" => 3));
$arr->each(function($key$val) { echo "$key: is $val\n"; });

and the result the same, of course. Or, let’s try with ranges:

class RubyRange {
    protected $_from$_to;
    public function __construct($from$to) {
        $this->_from $from;
        $this->_to $to;
    }
    public function each($body) {
        $iter = new RubyIterator($body);
        for($i=$this->_from$i<=$this->_to$i++) {
            $iter->yield($i);
        }
    }
    
}

and use it:

/*
r = 1..10;
r.each do |i|
    print "#{i*i}\n"
end
*/

$rr = new RubyRange(110);
$rr->each(function($i) { echo $i*$i."\n"; });

which indeed produces:

1
4
9
16
25
36
49
64
81
100

And so on – if one wanted, whole set of iterator methods could be implemented (I’m of course too lazy to do that :) ). I wonder if there are use cases that we can’t do there.

About these ads

26 Responses to “Ruby-like iterators in PHP”

  1. Neat idea, and one of the things I like about ruby as well. However, seems like a lot of work just to get that ruby feeling. Now if this flavor could be baked into the PHP iterator interface ( http://php.net/manual/en/class.iterator.php ) then we’d really be getting somewhere.

    Cheers,

    ~e

  2. Hi,

    I implemented all PHP types as well as some other additional types. It is nice to allow you to work with PHP data types as objects. Method calls are chainable which allows for some nice syntaxes:

    http://github.com/jwage/DurrProject

    $array = Type::arr()
    ->_explode(‘test1, test2, test3′)
    ->_reverse();

    $this->assertEquals(array(‘test3′, ‘test2′, ‘test1′), $array->getValue());

    • bungle said

      This is so much cleaner:
      $array = array_ reverse(explode(‘test1, test2, test3′));

      PHP has a great data structure called array. It also has huge number of inbuilt functionality to work with the arrays (http://www.php.net/manual/en/ref.array.php + iterators). Using objects to wrap this, is only obfuscating the whole thing. Introducing new unneeded needed datatypes (Type::arr()) etc. only binds your data (= array) with functionality. In C-like environments (that I count PHP too), it’s better to have less and pure data types (no data encapsulation) and more functions to work with them.

      • This blog is for thinking outside the box. Let go of your limited vision. The purpose of making this stuff is not with the intentions of people using it, it is just to help with expanding your thoughts. Think of new ways to do things. Don’t take it so personally and get so defensive :) Anyways, I wrote that code not for me to use, I wrote it because I was playing with PHP5.3 and needed some stupid pilot project to play with. Hence the name “Durr”…

        • Stas said

          Jonathan, you got it exactly right, thanks! We do some stuff just because we can, and it looks fun to. It doesn’t have to be all narrowly practical. If it will turn out practical – it’s a nice bonus :)

          • Bungle said

            First of all I didn’t took anything personally. I’m all into learning new ways and techniques. I only shared my point of view in the comments. That is the main reason of having comments in a blog – to get conversation of used technique.

  3. bungle said

    How this is different than using (these):

    http://www.php.net/manual/en/function.array-filter.php

    http://www.php.net/manual/en/function.array-walk.php

    http://php.net/manual/en/function.array-map.php

    http://php.net/manual/en/function.range.php

    Seems like totally unneeded bloat for me.

    • MEK said

      From the fucking article:

      So it got me thinking – suppose I wanted to do something like this in PHP (suppose I don’t like regular loop-y iterators for some weird reason). Naturally, I wouldn’t get it in the same concise form as Ruby does, since I can’t change the syntax. But I could get the essence. Let’s try it.

  4. What is there in Ruby iterators, that cannot be implemented using PHP foreach loop?

    arr.each do |key, val|
    print “#{key}: is #{val}\n”
    end

    foreach (arr as $key => $val) {
    echo “$key: is $val”;
    }

  5. bungle said

    And there are also already these:

    http://www.php.net/manual/en/spl.iterators.php

  6. Bungle, the magic is in the yield method… in ruby at least ;)

    • bungle said

      Bungle, the magic is in the yield method… in ruby at leas

      But this isn’t yielding… it’s just calling the passed callback. Nothing to do with yielding, even if you have method named yield. PHP doesn’t support yield, and it cannot be hacked in with classes. Only way to do it is to change the language to support it.

  7. harald said

    i still don’t get it … what’s the difference between your iterator class and for example:

    array_map(function(…) {
    }, …);

    ? what can do the ruby-iterator class better than array_map or the SPL iterators?

    • Stas said

      The difference is it doesn’t have to be an array and you can do other things beside iterating over elements in a linear fashion (see what they do in Ruby with iterators, simplest example would be .times, but there are much more complex ones – like walking a tree). It’s decoupling of data generation from the action.
      As for better – it’s in the eyes of beholder. I don’t think it makes sense to claim one way is “better” than other without saying better for whom and for what purpose.

  8. [...] this new post to his blog Stanislav Malyshev looks at creating some Ruby-like iterators as close as they can get [...]

  9. Ants Aasma said

    I don’t get what’s the point of RubyIterator? It doesn’t seem to have anything to do with iterators. It just wraps calling a function object as a single method interface. Seems kind of pointless with first class functions that you could call directly.

    What would be actually quite useful would be an SPL iterator implementation of Python’s itertools module providing lazy iterators for map, filter, slice, zip, chain and other operations. Some of them are actually there in SPL, but having to subclass the Iterator classes makes them hopelessly verbose and puts the relevant code out of band. Implementing them without the yield keyword is rather tedious. Whether they are methods on a collection class or just functions/static methods is only a syntactic difference – whether the actions to perform read outside in or inside out.

    The syntax would be even nicer if PHP supported destructuring assignment list($x,$y) in foreach.

  10. [...] With Zend Framework Wie man Zend_Form Formulare ein bisschen mehr wiederbenutzbarer gestalten. Ruby-like iterators in PHP « PHP 10.0 Blog Ruby-Iterator Style nach PHP konvertiert. Was haltet ihr davon? Share this on del.icio.usShare [...]

  11. As a professional PHP developer I am not sure where I would use this feature. I understand why you have pursued the idea and its nice to see thing like this from time to time. It could be a bit confusing though for newbies and as others have said its not really necessary. Although thats not the point is it I guess. Wait even I’m confusing myself now….lol

  12. Xerof3ar said

    Thanks for the inspiration! I have posted a more reusable solution if anyone wants it:

    http://pastebin.org/94122

  13. Sujai SD said

    First of all, good attempt and new trial on PHP.

    But to add more this,
    1. PHP and ruby are different and accepting the difference is what makes us code better in the specific language.
    2. We can inherit / welcome new features of other language, if it is really easy to use(like RoR based frameworks got into PHP), while the above makes an overload to make simple things available in PHP.

    Again, It is good and appreciative to try something new.. but why not try, that is really innovative and useful?

  14. [...] Ruby-like iterators in PHP [...]

  15. amit said

    Nice article very much helpful. You can also visit to more tutorial about php http://www.codesnipr.com/#!/tags/php

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: