max pleaner max pleaner - 7 months ago 17
Ruby Question

In a loop, pass as argument to `redo` to change the current iteration's element

I have heard that it is not possible to modify an array while looping over it. But I wanted to post here to check that I'm not missing some trick or something.

Let me just give a little code to explain. This method is supposed to take an array of "ingredients" which are slices of bread, and then make two changes: toast the bread, then add butter.

ingredients = ["bread", "toast"]
buttered_slices = ingredients.map do |ingredient|
if ingredient.eql?("toast")
next "buttered_toast"
else
# ideally the ingredient variable would be "toast"
# for the next iteration
redo "toast"
end
end


To be clear, this doesn't work. The
redo
doesn't care about the argument that it's given and the code enters an infinite loop.

I've edited this question a bit to make the code more concise and less pseudocode-like. I also changed the title to be more specific

Answer

Besides the approach provided by @Albin, one might achieve the same functionality with redo. You wrote “ideally "ingredient" would be ‘toast’ for the redo”— just do it;

buttered_slices = ingredients.map do |ingredient|
  if ingredient.is_a?(Toast)
    next ingredient.to_buttered_toast
  else
    ingredient = ingredient.to_toast
    redo
  end
end

As by documentation, redo preserves bindings, including bindings of local variables.

Frankly, I do not get what is the reason of so cumbersome approach:

buttered_slices = ingredients.map do |ingredient|
  (ingredient.is_a?(Toast) ? ingredient : ingredient.to_toast).to_buttered_toast
end