iswg
iswg

Reputation: 195

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?

Upvotes: 1

Views: 187

Answers (2)

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230346

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?

Upvotes: 1

tadman
tadman

Reputation: 211590

The main problem is the order of operations. When you call print it will print immediately, there's no delay, which can cause problems.

In Ruby it's often easier to deal with code that returns strings rather than code that causes side-effects like printing. If they return strings you have control over where that output goes. If they print things immediately you need to be very careful about the order you call them in.

The way you're calling that code in the final assembly with the tag(:ul) call is actually going to be trouble. The second version of your method coincidentally orders things correctly.

It's not necessarily easy to fix this. If you return a string, then only the last string from your three tag calls will be used. If you print, you'll have to be sure you're using the second method to make it work.

Within the Rails system there's a way of capturing the output of these things for buffering purposes, but that's a very messy thing to try and do, it can get really confused when you try and handle all cases.

Where possible create some kind of buffer these things can write to, then when everything's done write that out with print or whatever.

Upvotes: 1

Related Questions