iswg iswg - 4 months ago 14
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

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?

Comments