iswg iswg - 1 year ago 65
Ruby Question

Ruby iterator yield

I'm wondering why the following tag methods produce different results:

Method 1:

def tag(html)
print "<#{html}>#{yield}</#{html}>"
end


Method 2:

def tag(html)
print "<#{html}>"
print yield
print "</#{html}>"
end


When I ran the following code in terminal using the above methods:

tag(:ul) do
tag(:li) { "It sparkles!" }
tag(:li) { "It shines!" }
tag(:li) { "It mesmerizes!" }
end


The first one gave me:

<li>It sparkles!</li><li>It shines!</li><li>It mesmerizes!</li><ul></ul>


The second one gave me:

<ul><li>It sparkles!</li><li>It shines!</li><li>It mesmerizes!</li></ul>


The second one is the output that I'm looking.

How come the first method prints 'yield' before it prints what comes before 'yield' in the string?

Answer Source

Just to echo @tadman's answer: order of evaluation AND inconsistency of api. Your block sometimes returns strings and sometimes prints strings as a side-effect.

  print "<#{html}>"
  print yield
  print "</#{html}>"

Here you print, then yield. If the block returns a string (one of :li blocks), then it's printed right here. If it's a :ul block, then its side-effects happen (printing of li blocks) and nil is printed after that.

In the other case

print "<#{html}>#{yield}</#{html}>"

Ruby has to assemble one string to print. Which means yielding before any printing. Which means that side-effects happen before printing the opening <ul>. As the ul block returns nil, that's why it's printed empty at the end of the string (<ul></ul>).

Does it make sense?