Ruby, Nested Yields, and Implicit Return Values

This is one of the many reasons I cringe at languages like Ruby with implicit behaviors.  Take this example:

class DoStuff
  attr_reader :accum

  def initialize
    @accum = ''
  end

  def do_a(&block)
    @accum << 'a'
    @accum << yield
  end

  def do_b(&block)
    @accum << 'b'
    @accum << yield
  end
end

def fubar
  do_stuff = DoStuff.new

  do_stuff.do_a do
    do_stuff.do_b do
      "rec\r\n"
    end
  end

  puts (do_stuff.accum)
end

fubar

Quick, tell me why the return is:

abrec
abrec

The reason is because the outer call do_stuff.do_a() has an implicit return of the result of the call to do_stuff.do_b() and so also returns “rec”.

To fix this, one must explicitly return an empty string as the return:

  do_stuff.do_a do
    do_stuff.do_b do
      "rec\r\n"
    end
    ''
  end

and now the return is:

abrec

So, beware, especially beginner programmers, of the implicit return in Ruby functions.

Compare this with a C# implementation:

public class DoStuff
{
    public string Accum {get; protected set;}

    public DoStuff()
    {
        Accum = "";
    }

    public void DoA(Func a)
    {
        Accum.Append("a");
        Accum.Append(a());
    }

    public void DoB(Func b)
    {
        Accum.Append("b");
        Accum.Append(b());
    }
}

class Program
{
    static void Main(string[] args)
    {
        DoStuff doStuff = new DoStuff();
        doStuff.DoA(() => doStuff.DoB(() => "rec\r\n"));
        Console.WriteLine(doStuff.Accum.ToString());
   }
}

We get a compiler error:

Cannot implicitly convert type ‘void’ to ‘string’

This clearly tells us we have done something wrong.

If we change the return types to strings, then it becomes obvious (hopefully) that we want to return an empty string:

public string DoA(Func a)
{
    Accum.Append("a");
    Accum.Append(a());
            
    return "";
}

public string DoB(Func b)
{
    Accum.Append("b");
    Accum.Append(b());

    return "";
}

and we get the desired behavior.

We can of course write the code the illustrate the implicit return of the Ruby code, but of course the C# code clearly illustrates this (and therefore the error of our ways):

public string DoA(Func a)
{
    Accum.Append("a");
    Accum.Append(a());
            
    return Accum.ToString();
}

public string DoB(Func b)
{
    Accum.Append("b");
    Accum.Append(b());

    return Accum.ToString();
}

And indeed, we get:

abrec
abrec

just like in my “wrong” Ruby example.

One thought on “Ruby, Nested Yields, and Implicit Return Values

Leave a comment