Friday, September 28, 2007

Ruby's yield statement

Ruby provides a yield statement that eases the creation of iterators.

The first thing I noticed was that its behavior is different from the C# yield statement (which I knew from before).

Ruby's yield statement gives control to a user specified block from the method's body. A classic example is the Fibonacci Sequence:


class NumericSequences
def fibo(limit)
i = 1
yield 1
yield 1
a = 1
b = 1
while (i < limit)
t = a
a = a + b
b = t
yield a
i = i+1
end
end
...
end



The fibo method can be used by specifying a block that will be executed each time the control reaches a yield statement. For example:


irb(main):001:0> g = NumericSequences::new
=> #<NumericSequences:0xb7cd703c>
irb(main):002:0> g.fibo(10) {|x| print "Fibonacci number: #{x}\n"}
Fibonacci number: 1
Fibonacci number: 1
Fibonacci number: 2
Fibonacci number: 3
Fibonacci number: 5
Fibonacci number: 8
Fibonacci number: 13
Fibonacci number: 21
Fibonacci number: 34
Fibonacci number: 55
Fibonacci number: 89


The following example shows the sequence of numbers of a specific row of the Pascal's triangle :



def pascal_triangle_row(n)
for k in 0..n
yield(fact(n)/(fact(k)*fact(n-k)))
end
end



This method can be used like this:


irb(main):001:0> g = NumericSequences::new
=> #<NumericSequences:0xb7c9103c>
irb(main):002:0> (0..8).each {|n| g.pascal_triangle_row(n) {|r| print "#{r} "}; print "\n"}
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
1 6 15 20 15 6 1
1 7 21 35 35 21 7 1
1 8 28 56 70 56 28 8 1
=> 0..8


Methods using the yield statement can be combined. For example in order to create the sequence of Pascal's triangle read by rows we can write:


def pascal(limit_row)
i = 0
while (i <= limit_row)
pascal_triangle_row(i){|x| yield(x)}
i = i+1
end
end


And use it the same way:


irb(main):005:0> g.pascal(10) {|x| print "#{x} "}
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 7 21 35 35 21 7 1 1 8 28 56 70 56 28 8 1 1 9 36 84 126 126 84 36 9 1 1 10 45 120 210 252 210 120 45 10 1 => nil

6 comments:

Anonymous said...

Could you explain how this is different than C#'s yeild? It looks to be doing the same thing from the examples you gave. Am I missing something?

Luis Diego Fallas said...

Sorry I didn't explain why I think c#'s yield is different.

I think there's no big difference, but for me, the way you use the result of a method call is different.

In C# when you use yield the type of the method must be IEnumerable<T> where T is the type of the element you are returning. This means you can pass the result of a calling that method to another method or assign it to a variable. For example:

class P {

IEnumerable<int> Numbers()
{
yield return 1;
yield return 2;
yield return 3;
}

void UseNumbers()
{
IEnumerable<int> v = Numbers();
Foo(Numbers());
}

void Foo(IEnumerable<int> g)
{
foreach(int i in g ) {
Console.WriteLine(i);
}
}
}

From my limited Ruby knowledge, I think the difference is that in order to use a method that has yield you need to specify a block as an argument of the method call (like in the example I use in this post).

If you want to pass the result you can use Enumerator::Enumerable class.

r = P::new

def foo(e)
e.each { |i| print i}
end

en = Enumerator::Enumerable::new(r,:numbers)
foo(en)

Luis Diego Fallas said...

One think I didn't know how to implement using Ruby's yield is how to create an infinite sequence of values , combine it with other sequences and take only some elements of the sequence (which was the original example I was trying to implement for this post). For example in C# you can write:

IEnumerable<int> Odd()
{
int i = 1;
while(true)
{
if (i % 2 != 0) {
yield return i;
}
i++;
}
}
IEnumerable<int> Even()
{
int i = 1;
while(true)
{
if (i % 2 == 0) {
yield return i;
}
i++;
}
}
IEnumerable<T> Mix<T>(IEnumerable<T> e1,IEnumerable<T> e2)
{
IEnumerator<T> i1 = e1.GetEnumerator();
IEnumerator<T> i2 = e2.GetEnumerator();
bool c1 = true;
bool c2 = true;
while (c1 || c2)
{
if (i1.MoveNext())
{
yield return i1.Current;
} else {
c1 = false;
}
if (i2.MoveNext())
{
yield return i2.Current;
} else {
c2 = false;
}
}
}

IEnumerable<T> Take<T>(int n,IEnumerable<T> e) {
int i =1;
foreach(T r in e)
{
if (i > n)
break;
yield return r;
i++;
}
}

You can write the following code:

foreach(int i in Take(6,Mix(Odd(),Even())) ) {
Console.WriteLine(i);
}


And the program will print only the numbers from 1 to 6.

I'm going to read more to see if this could be implemented.

Jules said...

Here's a neater version of fibo:

def fibs(x)
a = b = 1
x.times{yield a; a,b = b,a+b}
end

to be used like this:

fibs(10){|fib| puts fib}

Luis Diego Fallas said...

Very nice!

Xetas said...

As far as I know yield in Ruby is just syntax sugar for calling implicit last lambda parameter. So it's less powerful then Python's or C#'s analogs. E.g. in python one can write following, but not in Ruby:

def indices(n):
. i = 0
. while n>i:
.. yield i
.. i += 1

def fib():
. a = b = 1
. while True:
.. yield a
.. a,b = b,a+b

list(zip(index(5), fib()))