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.