Why does uniq! return nil if there are no duplicates
I'm just starting with Ruby and I personally find the following to be a violation of the "principle of least surprise". And that is, quoting from the documentation, that uniq! "removes duplicate elements from self. Returns nil if no changes are made (that is, no duplicates are found)."
Can anybody explain this, which seems completely counter-intuitive to me? This means that rather than being able to write one line of code below by appending .uniq! to end the first line, I instead have to write the following two lines:
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks = hooks.uniq
Or am I missing something, a better way?
EDIT:
I understand that uniq! modifies its operand. Here's the problem illustrated better I hope:
hooks = IO.read(wt_hooks_impl_开发者_开发技巧file).scan(/wt_rt_00\w{2}/)
puts hooks.length #50
puts hooks.uniq!.length #undefined method `length' for nil:NilClass
I contend that the way uniq! works makes it completely senseless and useless. Sure in my case as pointed out I could just append .uniq to the first line. However later in the same program I am pushing elements onto another array inside of a loop. Then, under the loop, I'd like to "de-dupe" the array, but I dare not write 'hooks_tested.uniq!' because it could return nil; instead I must write hooks_tested = hooks_tested.uniq
Indeed I contend this is a particularly egregious mis-feature in that it is a well known principle that, when devising a method that returns an array, one should always at least return an empty array, rather than nil
This is because uniq!
modifies self
and if uniq!
would return a value you wouldn't be able to know whether a change actually occurred in the original object.
var = %w(green green yellow)
if var.uniq!
# the array contained duplicate entries
else
# nothing changed
end
In your code you can simply write
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks.uniq!
# here hooks is already changed
If you need to return the value of hook perhaps because it's the last method statement just do
def method
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks.uniq
end
or otherwise
def method
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks.uniq!
hooks
end
The exclamation point on uniq!
indicates that it modifies the array instead of returning a new one. You should do this:
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/).uniq
or this
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks.uniq!
puts hooks.length
Since Ruby 1.9, Object#tap
is available:
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/).tap do |hooks|
hooks.uniq!
end
puts hooks.length
And perhaps more succinctly (h/t @Aetherus):
hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/).tap(&:uniq!)
puts hooks.length
You can append uniq
(no exclamation mark at the end) to the end of the first line.
Or, if you insist on using uniq!
, use
(hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)).uniq!
This is not an answer to why, but rather, a workaround.
Since uniq
doesn't return nil
, I use uniq
and assign the the result to a new variable instead of using the bang version
original = [1,2,3,4]
new = original.uniq
#=> new is [1,2,3,4]
#=> ... rather than nil
Having a new variable is a small price to pay. It sure as hell beats doing if checks, with repeated complex calls to uniq!
and uniq
and checking for nil
精彩评论