#ruby
Вопрос:
Итак, я пытаюсь исправить класс, чтобы он мог использоваться для хэш-ключа. Однако, похоже, я не могу заставить его работать. Вот пример тестового случая:
class Tmp
def hash; rand(); end
end
module Patch
refine Tmp do
def hash; 1; end
def eql?(rhs); hash == rhs.hash; end
end
end
puts "=========================="
module A
# Using the initial implementation of Tmp
x = {}
x[Tmp.new] = 1
x[Tmp.new] = 2
puts "#{Tmp.new.hash == Tmp.new.hash}"
puts "#{Tmp.new.eql? Tmp.new}"
puts "#{x}"
end
puts "=========================="
module A
# Enabling the refinement of class Tmp here
using Patch
x = {}
x[Tmp.new] = 1
x[Tmp.new] = 2
puts "#{Tmp.new.hash == Tmp.new.hash}"
puts "#{Tmp.new.eql? Tmp.new}"
puts "#{x}"
end
Это дает результат:
==========================
false
false
{#<Tmp:0x0000407e4ae818>=>1, #<Tmp:0x0000407e4ae7a0>=>2}
==========================
true
true
{#<Tmp:0x0000407e4ae188>=>1, #<Tmp:0x0000407e4ae110>=>2}
Как вы можете видеть, методы #eql?
и #hash
работают так, как ожидалось, но когда я пытаюсь использовать их в качестве хэш-ключа, он думает, что это разные ключи. Что я делаю не так?
Редактировать
В качестве отступления, в документах говорится:
Два объекта ссылаются на один и тот же хэш-ключ, если их хэш-значение идентично и два объекта являются eql? друг другу.
Определенный пользователем класс может использоваться в качестве хэш-ключа, если хэш и eql? методы переопределяются, чтобы обеспечить осмысленное поведение.
(Emphasis mine)
It’s interesting that that only #eql?
seems to be used to determine key equality:
class Tmp
def hash; rand; end
def eql?(rhs); true; end
end
puts "=========================="
module A
x = {}
x[Tmp.new] = 1
x[Tmp.new] = 2
puts "#{Tmp.new.hash == Tmp.new.hash}"
puts "#{Tmp.new.eql? Tmp.new}"
puts "#{x}"
end
Дает:
==========================
false
true
{#<Tmp:0x0000407e407b08>=>2}
Возвращаемое значение #hash
не имеет значения:
class Tmp
def hash; 1; end
def eql?(rhs); false; end
end
puts "=========================="
module A
x = {}
x[Tmp.new] = 1
x[Tmp.new] = 2
puts "#{Tmp.new.hash == Tmp.new.hash}"
puts "#{Tmp.new.eql? Tmp.new}"
puts "#{x}"
end
Дает:
==========================
true
false
{#<Tmp:0x0000407e40e408>=>1, #<Tmp:0x0000407e40e3b8>=>2}
Что, похоже, противоречит документам. Является ли это артефактом использования ruby v2.2.4? (Я использую ruby, который поставляется с Sketchup 2017)
Комментарии:
1. Примечание: Наличие случайного
hash
значения может вызвать проблемы. Он должен возвращать одно и то же значение каждый раз, когда он вызывается для одного и того же объекта, хранящего одни и те же данные.2. @tadman я использовал
rand
только для примера. Обычно я бы так не поступил.3. Да, просто пытаюсь понять, в чем здесь смысл.
4. Прочитав это несколько раз, я все еще не уверен, в чем заключается вопрос. Этот последний пример работает так, как ожидалось, вы заставили их не быть
eql?
, поэтому вы получаете два отдельных ключа.5. @tadman, да, но я ожидал бы, что 2-й последний пример также должен иметь те же результаты, что и последний пример, поскольку
#hash
не дает постоянного значения, даже если#eql?
даетtrue
.
Ответ №1:
Вы реализуете свой класс с явно непоследовательным поведением. Для любого объекта, где a.eql?(b)
тогда по определению a.hash == b.hash
as #hash
должно отражать фактическое содержимое объекта. Если этого не произойдет, вы получите очень странные результаты.
Это похоже на то, как при написании компаратора сортировки, если a < b
и b < c
тогда a < c
должно быть верно. Если по какой-то причине это не так, то ваши операции сортировки приведут к очень странным и противоречивым результатам.
Другими словами:
a.eql?(b)
должно быть истинно тогда и только тогда, когдаa
иb
представляет собой одно и то же.a.hash == b.hash
должно быть правдой, еслиa
иb
представлять одно и то же.
Обратите внимание, что a.hash == b.hash
это не означает, что они обязательно одно и то же, но это указывает на высокую вероятность того, что это так. Для eql?
подтверждения этого всегда необходима дополнительная проверка.
Внутренние хэш-функции могут вызывать или не вызывать обе эти функции. При небольших размерах рубиновый «Хэш» вообще не является хэш — таблицей, это просто крошечный массив. Как таковой, #hash
может даже не вызываться до тех пор, пока таблица не достигнет определенного размера, и этот метод имеет значение.
Комментарии:
1.Вы, как видно, упускаете главное. В последней половине (после редактирования) были сделаны выводы, которые не соответствовали документам, а именно это
#eql?
и#hash
должно быть правдой. Учитывая это, в обоих приведенных примерах каждый объект должен рассматриваться как отдельный ключ. Кроме того, это было в стороне. Как раз перед тем, как изменить формулировку фактического вопроса. Чтобы было понятно, почему уточнение используется не для ключа хэша, а при непосредственном использовании методов.2. Я обновил свой вопрос, чтобы, надеюсь, подчеркнуть соответствующие моменты.
3. Я не уверен, что уточнения будут работать здесь, поскольку он
Hash
сам вызывает эти методы, что выходит за рамки уточнения.4. Да, я только что это понял. Он
using
ограничен областью действия модуля или класса, в котором я его использую, и только с этого момента и до закрытия этого модуля или блока класса. Похоже, то же#to_s
самое происходит и при автоматическом преобразовании в строку. Прискорбно. 🙁5. Да, он чрезвычайно локализован по сути и не влияет на другие исходные файлы.