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(1, 10); $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.