Почему uniq! return nil, если нет дубликатов

Я только начинаю с Ruby, и я лично считаю следующее нарушением "принципа наименьшего удивления". И это, цитируя документацию, что uniq! "удаляет повторяющиеся элементы из себя. Возвращает nil, если никаких изменений не производится (то есть, не найдены дубликаты).

Может кто-нибудь объяснить это, что кажется мне совершенно интуитивным? Это означает, что вместо того, чтобы писать одну строку кода ниже, добавив .uniq! для завершения первой строки я должен написать следующие две строки:

  hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
  hooks = hooks.uniq

Или я что-то упускаю, лучший способ?

EDIT:

Я понимаю, что uniq! изменяет его операнд. Здесь лучше иллюстрируется проблема, я надеюсь:

  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

Я утверждаю, что путь uniq! работы делают его совершенно бессмысленным и бесполезным. Конечно, в моем случае, как указано, я мог просто добавить .uniq в первую строку. Однако позже в той же программе я нажимаю элементы на другой массив внутри цикла. Затем, под циклом, я хотел бы "де-обмануть" массив, но я не смею писать "hooks_tested.uniq!" потому что он мог бы вернуть нуль; вместо этого я должен написать hooks_tested = hooks_tested.uniq

Действительно, я утверждаю, что это особенно вопиющая неправильная особенность в том, что хорошо известный принцип заключается в том, что при разработке метода, который возвращает массив, всегда нужно как минимум возвращать пустой массив, а не nil

Ответ 1

Это связано с тем, что uniq! изменяет self, и если uniq! вернет значение, вы не сможете узнать, действительно ли произошло изменение исходного объекта.

var = %w(green green yellow)
if var.uniq!
  # the array contained duplicate entries
else
  # nothing changed
end

В коде вы можете просто написать

hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks.uniq!
# here hooks is already changed

Если вам нужно вернуть значение hook, возможно, потому что это последний оператор метода просто

def method
  hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
  hooks.uniq
end

или иначе

def method
  hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
  hooks.uniq!
  hooks
end

Ответ 2

Восклицательный знак на uniq! указывает, что он изменяет массив вместо того, чтобы возвращать новый. Вы должны сделать это:

hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/).uniq

или

hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)
hooks.uniq!
puts hooks.length

Ответ 3

Вы можете добавить uniq (восклицательный знак в конце) в конец первой строки.

Или, если вы настаиваете на использовании uniq!, используйте

(hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/)).uniq!

Ответ 4

Так как Ruby 1.9, Object#tap доступен:

hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/).tap do |hooks|
  hooks.uniq!
end
puts hooks.length

И, возможно, более кратко (h/t @Aetherus):

hooks = IO.read(wt_hooks_impl_file).scan(/wt_rt_00\w{2}/).tap(&:uniq!)
puts hooks.length

Ответ 5

Это не ответ на вопрос, а скорее обходной путь.

Так как uniq не возвращает nil, я использую uniq и присваиваю результат новой переменной вместо использования версии bang

original = [1,2,3,4]
new = original.uniq

#=> new is [1,2,3,4]
#=> ... rather than nil

Наличие новой переменной - небольшая цена для оплаты. Конечно, черт побери, если чеки, с повторными сложными вызовами на uniq! и uniq и проверка на nil