1

I am working on learning some Hash operations in ruby. The code is about increasing each item's price by 10%. Why doesn't this code work?

restaurant_menu = { "Ramen" => 3, "Dal Makhani" => 4, "Coffee" => 2 }
restaurant_menu.each do |item, price|
  price = price + (price * 0.1)
end

while this one does:

restaurant_menu = { "Ramen" => 3, "Dal Makhani" => 4, "Coffee" => 2 }
restaurant_menu.each do |item, price|
  restaurant_menu[item] = price + (price * 0.1)
end

And any reasons as to why the latter is a better way to do it than the former as explained by @Mike Manfrin?

sawa
  • 165,429
  • 45
  • 277
  • 381
Anony-mouse
  • 2,041
  • 2
  • 11
  • 23
  • Yet another Ruby question arising from a misunderstanding of how object mutation works. – Ajedi32 Jun 08 '15 at 19:07
  • Can you explain or give some links so i can understand object mutation in ruby.I want to close this question as answered then.@Ajedi32 – Anony-mouse Jun 08 '15 at 19:09
  • That's actually kind of why I mentioned it; I don't know of a canonical source explaining this concept. – Ajedi32 Jun 08 '15 at 19:13
  • @Ajedi32 . Any source would suffice canonical or non-canonical even chat messages would do.I want to understand why it happens.Seems kinda meta ruby now. – Anony-mouse Jun 08 '15 at 19:28
  • This is probably the closest thing I can find. The question isn't really the same, but the answers do go into a bit of detail about how mutation works: https://stackoverflow.com/questions/1872110/is-ruby-pass-by-reference-or-by-value – Ajedi32 Jun 08 '15 at 19:35

1 Answers1

5

In the first one, you're setting the local variable of price to your new price, and then it gets discarded. item and price are scoped only to that one line they're used on.

In your second example, you are setting a variable, restaurant_menu, that exists outside the each block, so those changes will persist after the each block has finished running.

Mike Manfrin
  • 2,722
  • 2
  • 26
  • 41
  • @MikeManfrin.That was fast but shouldn't what we write to price in its local scope be written to its Global parent object ? – Anony-mouse Jun 08 '15 at 18:57
  • @Anony-mouse I get what you're trying to say, but there's no such thing as a "global parent object". Ruby just doesn't work that way. Do you have an example a language that does? – Ajedi32 Jun 08 '15 at 19:06
  • @Ajedi32.Thanking you for explaining me that.But what are advantages to this way than the first one? – Anony-mouse Jun 08 '15 at 19:08
  • There's no advantages -- they do different things. In the first you are iterating over each key/value pair (to do whatever you want), in the second one you're doing the same but also setting the price of each item in your outer-scoped hash. – Mike Manfrin Jun 08 '15 at 19:17
  • Hi @Anony-mouse from the documentation at http://ruby-doc.org/core-1.9.3/Hash.html#method-i-each your code "Calls block once for each key in [the hash], passing the key-value pair as parameters." Here, parameters means local variables essentially, and so they will 'vanish' once the block is completed unless you have stored them somewhere in a higher scope that can 'escape' the scope of the block. – dave_slash_null Jun 08 '15 at 19:18
  • @Anony-mouse You mean why is the language designed this way? Hmm, well I've never really seen an example of a language with a different behavior, so it's hard for me to say. My gut feeling is that, depending on the exact implementation, a language behaving the way you described would make it very easy to accidentally modify variables that you didn't intent to. I can't really say for sure though. – Ajedi32 Jun 08 '15 at 19:18
  • Also, `each` tends to be non-mutative; if you are changing values of a certain enumerable, `map` is the proper method. E.g., on arrays, `each` and `map` both iterate the same, but `map` returns a new version of the given array with the modified values (and `map!` will save to the original variable). – Mike Manfrin Jun 08 '15 at 19:29
  • Thank you @DRSE ,@Mike Manfrin That was great i think i can close this question now.Should i wait ? – Anony-mouse Jun 08 '15 at 19:33
  • 1
    @MikeManfrin Sorry i wanted to upvote the answer but i think i donot have enough reputations. – Anony-mouse Jun 08 '15 at 19:36
  • 1
    @Anony-mouse Questions on StackOverflow don't get "closed" when they're answered. Closing is reserved for questions that don't meet the quality standards of the site. Instead, you should accept an answer by clicking the check mark right below the voting buttons. (Or, if there is no answer which you believe answers your question, just leave the question open with no accepted answer.) – Ajedi32 Jun 08 '15 at 19:39
  • @Ajedi32 Done since i cant upvote anyways,I think i should do that. – Anony-mouse Jun 08 '15 at 19:40