Iterators in C#, Python, and Ruby

Michael Geary | Mon, 2004-08-02 17:33

Matt Pietrek marvels at C# 2.0 iterators and dissects them right down to the CLR bytecode. I always learn something from Matt, and this whirlwind tour is no exception.

Matt says, “This was the beginning of my descent into the loopy world of C# 2.0 iterators. It took me awhile to wrap my head around them, and when I tried to explain them to other team members I got looks of total confusion.” I wonder if it would have been less confusing if Matt’s team had first been exposed to yield iterators in a language that makes them easier to use.

After using Python and Ruby, the iterators in C# feel right at home to me. They work the same in all three languages, but in Ruby and Python there’s not as much other code to get in the way of understanding them.

Let’s combine all of Matt’s examples into one, and compare the code in each language. First, in C#:

using System;
using System.Collections.Generic;

class SomeContainer
{
    public IEnumerable<string> MyIterator()
    {
        Console.WriteLine( "Before First" );
        yield return "First";
        Console.WriteLine( "After First" );

        for ( int i = 0;  i < 3;  i++ )
        {
            yield return i.ToString();
        }

        Console.WriteLine( "Before Last" );
        yield return "Last";
        Console.WriteLine( "After Last" );
    }
}

class foo
{
    static void  Main()
    {
        SomeContainer container =
            new SomeContainer();

        foreach( string s in
                container.MyIterator() )
            Console.WriteLine( s );
    }
}

When you run that, it should print:

Before First
First
After First
0
1
2
Before Last
Last
After Last

Here’s how you would write the same code in Python:

def MyIterator():
    print "Before First"
    yield "First"
    print "After First"

    for i in xrange(3):
        yield str(i)

    print "Before Last"
    yield "Last"
    print "After Last"

for s in MyIterator():
    print s

And in Ruby, the code looks like this:

def MyIterator()
    p "Before First"
    yield "First"
    p "After First"

    3.times do |i|
        yield i.to_s
    end

    p "Before Last"
    yield "Last"
    p "After Last"
end

MyIterator do |s|
    p s
end

The one unfamiliar thing here may be the |name| notation, which is how a code block such as the body of a loop receives its argument. And the p statements are a kind of print statement.

This Ruby version is even more concise and equally readable once you’re comfortable with the |name| notation:

def MyIterator()
    p "Before First"
    yield "First"
    p "After First"

    3.times { |i| yield i.to_s }

    p "Before Last"
    yield "Last"
    p "After Last"
end

MyIterator { |s| p s }

Either way, the Python and Ruby versions make it easier to see what the iterator function does and how yield interacts with the rest of the code.

You may note that the Python and Ruby versions don’t create and instantiate a SomeContainer class as the C# version does. That’s true, and it would make the code in those languages a bit longer (but still simpler than the C# code). But, if you don’t need to—and you especially don’t need to when you’re experimenting and trying to understand a radical new technique like yield iterators—why bother?

Submitted by Under The Hood - Matt Pietrek (not verified) on Mon, 2004-08-02 18:31.

Iterators in C#, Python, and Ruby

Submitted by Under The Hood - Matt Pietrek (not verified) on Tue, 2004-08-03 01:02.

Michael Geary returns

Submitted by Another Tired Idea (not verified) on Tue, 2004-08-03 02:33.

Let’s iterate over this topic one more time

Submitted by mike roome (not verified) on Tue, 2004-08-03 04:46.

The ruby example isn’t exactly equivalent, from what i understand of ruby — a block defines an anonymous function, which is passed into the method being called, and can be called within the function with ‘yield’. The end result is more or less the same, but the method by which the result is achieved is completely different.

For what it’s worth, you could semantically equivalent to the ruby example in C# as well, using anonymous delegates:

using System;

class C {
    /**
     * delegate void Action<t>(T obj); is a standard delegate provided by the
     * 2.0 class library. Others include:
     * bool Predicate<t>(T obj);
     * U Converter<t ,U>(T from);
     * int Comparison<t>(T x, T y);
     * see http://blogs.msdn.com/kcwalina/archive/2004/06/22/162533.aspx
     * for more details
     *
     * also, since 'yield' isn't a keyword on its own (it's only special in a
     * 'yield return' or 'yield break' statement), i decided to call the
     * delegate 'yield', to make the correspondence to the ruby clearer.
     **/
    public static void MyIterator(Action<string> yield) {
        Console.WriteLine("Before First");
        yield("First");
        Console.WriteLine("After First");

        for(int i = 0; i < 3; i++) yield(i.ToString());

        Console.WriteLine("Before Last");
        yield("Last");
        Console.WriteLine("After Last");
    }

    public static void Main() {
        MyIterator(delegate(string s) {
            Console.WriteLine(s);
        });
    }
}

using System;

    for(int i = 0; i < 3; i++) yield(i.ToString());

    Console.WriteLine("Before Last");
    yield("Last");
    Console.WriteLine("After Last");
}

public static void Main() {
    MyIterator(delegate(string s) {
        Console.WriteLine(s);
    });
}

}

(note: i haven’t tested this, but it should work on Beta1, afaik).

Submitted by mike roome (not verified) on Tue, 2004-08-03 04:48.

Submitted by mike roome (not verified) on Tue, 2004-08-03 04:52.

argh. sorry about this. it doesn’t seem to like unescaped <s… and again, this time without the huge, and now utterly redundant comment: using System; class C { public static void MyIterator(Action yield) { Console.WriteLine("Before First"); yield("First"); Console.WriteLine("After First"); for(int i = 0; i < 3; i++) yield(i.ToString()); Console.WriteLine("Before Last"); yield("Last"); Console.WriteLine("After Last"); } public static void Main() { MyIterator(delegate(string s) { Console.WriteLine(s); }); } }

Submitted by Elton Wells (not verified) on Mon, 2004-08-09 13:20.

Exploring C# 2.0 iterators

Submitted by Michael Geary (not verified) on Mon, 2004-08-09 17:34.

Elton, your trackback had an invalid URL:

http://arachnid/Blogs/ewells/archive/2004/08/09/435.aspx

Want to try again?

Submitted by femto (not verified) on Tue, 2005-05-10 11:33.

Well, yield in ruby surely don’t equal to function in javascript or delegate in C#. block in ruby is a closure, which means it use the current context where it’s in, it means sth like this:

def fun1 fun2 end def fun2 fun3 { return “hello1”; }

fun3 { return “hello2”; }

end def fun3 yield end result = fun1

puts result #—>”hello1”

###################################3

the first block in fun2 will just return “hello1” to the caller fun1, and skip the second block invocation, but with function pointer or delegate, you sure can’t do this, with return keyword in function pointer or delegate, you just return to where you call the function pointer or delegate.

That’s the differnce between them. the block can decide whether it should return some value to uplevel but function pointer or delegate can’t