Teach me about yield

i’ve programmed computers for the greater portion of my life and find
myself really “set in my ways” when it comes to solving problems. i
have both Dave and Matz’s ruby books and have read the description of
yield numerous times, yet never used a single yield statement in just
over a year of ruby coding.

when do you use yield? has it solved problems for you when you
couldn’t find another solution? how did you first start using yield?

i want to expand my mind and wrap it around this concept, any pointers
(besides RTFM), or sample code would be greatly appreciated

Joseph Benik wrote:

when do you use yield? has it solved problems for you when you
couldn’t find another solution? how did you first start using yield?

No, but it makes life sometimes much simpler. (After all
you could solve everything in machine code also…)

Assume that you have some data structure (e.g. a list,a tree,
a database,a string, a chessboard, whatever)
and you want to allow clients to iterate over its elements
(list entries, tree entries, database entries,
characters of the string, lines of the string, pieces on the
chess board,…).

In C, C++ or Java you would use either

  1. Function pointer
  2. Iterator Object
  3. Cursor Object.

Example:

You want to iterate over a list (For simplicity, I took list,
but you typically have some much more complicated container class.)

···

Solution 1) With function pointer:

You could write in C:

void List_Iter(List_t list,
void (apply)(void obj,va_list args),
…)
{
va_list args;
/
blah blah /
va_start(apply,args);
for ( /
* blah blah */ )
{
apply(obj,args);
}
va_end(args);
}

Usage:
a) For each iteration,
you have to define a new iterator function
you are passing List_Iter.

Drawback:
a) You have to write A LOT OF code.
b) The va_list is nasty (no type checking!)
(You can use a void * context instead, but it has
the same problem.)


Solution 2) Apply class;

You write

class List_Applier
{
public:
virtual void call(void *obj)=0;
};

class List
{
public:
// Lot of blah blah…
void iter(List_applier &apply)
{
// Blah Blah
for( /* blah blah */ )
{
apply.call(obj);
}
}
};

Usage: For each iteration you have to introduce a
new class, override the call method.

Advatage: (to solution 1)): Type safe: no nasty va_list, (or
void * context).

Drawback: Even MORE lines of code than solution 1).
You have to write about 20 lines for a very
simple iteration!


Solution 3) Cursor. (In C++:)

class List
{
public:
class Cursor
{
List::Entry *entry;
public:

       Cursor(List &list)
       {
          // Blah blah
       }

       void next()
       {
          /** Blah blah */
       }

       bool valid()
       {
          return (entry!=(List::Entry*)0);
       }
   };

};

Usage :
You write
for( Cursor cur(list); cur.valid() ; cur.next()
{
// DO something…
}

Advantage (to 1) 2)) Much less work to use. (Only 1 line instead of
about 20).

Drawbacks: (Still, it is too much for a simple iteration.)
a) A LOT_OF code to write the iterator. (A new
class and 3 new methods)
b) There is no standard way in the language: you have
to remember the way how to iterate over your particular
data structure.
c) It is not always simple to iterate over a data structure
by using cursor and next (e.g. you would implement
an iterator over a tree by a recursive function.)


Solution 4):
The Ruby way (via yield, simply linked list):
(A complete implemenatation for a change):

Example A):

class List
def add(obj)
@first = [@first,obj]
end

def each
e = @first
while e
yield e[1]
e = e[0]
end
end
end

l = List.new

l.add(“hello”)
l.add(“world”)

l.each do |s|
puts s
end

The yield in the “each” function calls the block for each object of
the list. The example above prints:
hello
world

Drawbacks: none.


Example B): Traversing a data structure recursively:

class Array
def traverse_rec
for e in self do
if e.kind_of? Array
e.traverse_rec
else
yield e
end
end
end
end

[ [1,2] [1,3,[4,[5] ],2,[3,4] ] ].traverse_rec do |el|
puts el;
end

outputs all elementes of the tree recursively.
(The tree is implemented as an array of (possible arrays of
(possible …))… )


Example C)

Do something with each element of the a data structure
(minor variation of example 1):

class List
def add(obj)
@first = [@first,obj]
end

def map
e = @first
while e
e[1] = yield e[1]
end
end
end

l = List.new

l.add(“hello”)
l.add(“world”)

l.map do |s|
s.length
end

Each element of the list is replaced by its length.


Example D)

Do something according to a dynamic criterion

(Sum up all elements of a list conditionally)

class List
def add(*objs)
objs.each do |obj|
@first = [@first,obj]
end
end

def sum_if
e = @first
sum = 0;
while e
sum += e[1] if yield e[1]
end
end
end

l = List.new
l.add(1,3,4,5,6,2,1,6);

l.add_if do |i|
(i%3)==0
end

The last function sums all elements of the list which
are divisible by 3.

I hope it gives you some insides about the power
of yield.

Regards, Christian

i’ve programmed computers for the greater portion of my life and find
myself really “set in my ways” when it comes to solving problems. i
have both Dave and Matz’s ruby books and have read the description of
yield numerous times, yet never used a single yield statement in just
over a year of ruby coding.

when do you use yield? has it solved problems for you when you
couldn’t find another solution? how did you first start using yield?

Here’s a way to look at it.

Do you find “each” useful? And other iterators?

Have you ever wanted to write an iterator for a class
of your own (that didn’t inherit from a class already
having one)?

In a case like that, you would use yield.

Have you ever wanted to write (what I call) a “non-iterating
iterator” that does some housekeeping, calls a block, and
does more housekeeping? Then you would use yield.

Definitive examples of the non-iterating iterator:
File.open(“file”) { block } # open, do block, close
Mutex.synchronize { block } # grab, do block, release
Dir.chdir(“dir”) { block } # cd, do block, cd back

If you don’t understand how this works, try this:

def do_something
puts “Before”
yield # Basically call the block
puts “After”
end

do_something { puts “I’m doing something” }

Output:
Before
I’m doing something
After

Secondly, note that if you want an “iterating” iterator, you
would do a yield inside a loop. Control is traded back and
forth between the iterator and its block.

Thirdly, note that yield returns a value (which is the last
expression evaluated inside the block). This is useful for
things like Enumerable#select and others.

i want to expand my mind and wrap it around this concept, any pointers
(besides RTFM), or sample code would be greatly appreciated

There’s nothing wrong with RTFM (Ruby Training From Matz). :slight_smile:

HTH,
Hal

···

----- Original Message -----
From: “Joseph Benik” jobeicus@hotmail.com
Newsgroups: comp.lang.ruby
To: “ruby-talk ML” ruby-talk@ruby-lang.org
Sent: Monday, September 23, 2002 7:15 AM
Subject: Teach me about yield