New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Document possible pitfall in each_with_object iteration #4561
base: master
Are you sure you want to change the base?
Conversation
Consider the following code: ```ruby ["a", "b", "c"].each_with_object([]) do |x, acc| if x == "b" acc = ["potatoes"] else acc << "#{x}-ok" end end ``` This returns `["a-ok", "c-ok"]`, although `["potatoes", "c-ok"]` might be expected. This is infuriating to debug, because all indications are that the assignment was successful. The proper approach is to avoid assignment to the `memo_obj` at all costs. ```ruby ["a", "b", "c"].each_with_object([]) do |x, acc| if x == "b" acc.replace(["potatoes"]) else acc << "#{x}-ok" end end ```
@jeremyevans I see you have reviewed other contributions to documentation. It looks like I can't request a review from you on this PR via the interface. |
I'd actually support making it clearer that only changes to the object, not assignment to the variable, have lasting effects. The reason is that there are similar methods (the most prominent being |
Assignments to argument(s) can never have effects outside the block. In general, that assignment in a local scope has no effect outside is elementary and very common in modern programing languages, except for special cases such as assignment operators in C++. |
There are 2 reasons I refer to documentation:
This was an example of that second case. I've been programming in Ruby for about a decade, and in this instance -- against the backdrop of the code in context -- the "beginner error" that I'm making wasn't obvious. (It is, of course, much easier to see in hindsight, especially when posted in minimal form.) But the question is worth asking: is there a reason that There is every reason to close this bug with the comment "well, Ian is just an idiot sometimes and he shouldn't be". But I opened this PR to express the manner in which I found the documentation lacking. |
@ianfixes - This is an elementary level information about the assignments which should be covered in another place if hasn't already been as this behavior is nothing different than just local variable assignment, like so; def foo(var)
var = :zoo
end
var = :bar
foo(var)
puts var # bar |
Respectfully, I think you are missing my point. We agree that there is no need to put beginner-level instruction on the nature of assignment in ruby into the documentation of What I'm suggesting is that the documentation cover a pitfall which is specific to this function: if you need to change the value of the "arbitrary object", be sure to use an object method (like |
Reword the function documentation to add clarity while avoiding giving away any hints about how assignment in ruby affects the use of this function in practice
I think the others consider that it is a part of “the nature of assignment” and not specific to this method. |
@jeremyevans what do you think of the updated text? |
I think it's a bit long. A bigger issue I see is with the call_seq, which doesn't make it obvious that the argument passed to the method is the same as the second argument passed to the block. Maybe:
|
Again, respectfully, you are talking past me. When I read the
Would you agree that one of these things is not like the others? |
Co-authored-by: Marivaldo Cavalheiro <[email protected]>
I considered doing this, but thought that it would be confusing (in the sense that it looks like shadowing). Can you confirm if that's OK here? Let me see about shortening the text. |
I think in this case it is fine, and properly shows that the method parameter, second block argument, and return argument are all the same object. However, other committers may have different opinions. I think @nobu's point is not about def foo(bar)
bar = 2
end
bar = 3
foo(bar)
bar # 3, not 2 You are stating this is a pitfall specific to |
Might as well show all my work. I'm working from the existing documentation for
This documentation uses
Is that better? I feel like |
The reason this is bad in
No, I don't think that is better. It misses the primary point of my recommendation, which is to reflect in the call-seq that the object passed to the method is yielded as the second argument to the block and also the return value. I don't think |
* <i>obj</i>) and the element. At the end of the iteration, <i>obj</i> | ||
* is returned. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The order of block parameters is reverse of inject
.
* <i>obj</i>) and the element. At the end of the iteration, <i>obj</i> | |
* is returned. | |
* <i>obj</i>) in addition to the element. At the end of the iteration, | |
* <i>obj</i> is returned. |
It sounds like 2 maintainer-acceptable options are being proposed for this text: @jeremyevans version
vs @nobu version
Which one should I go with? |
Bumping this, please let me know if I should wait more than a week before bumping it again |
This patch only updates code comments (for documentation purposes), not code.
Consider the following code:
This returns
["a-ok", "c-ok"]
, although["potatoes", "c-ok"]
might be expected. This is infuriating to debug, because all indications are that the assignment was successful.The proper approach is to avoid assignment to the
memo_obj
at all costs.I've updated the documentation to mirror that of
inject
, which offers more clarity into the nature of the object being passed in:ruby/enum.c
Lines 877 to 883 in 9210f8d