3

添加到存储在哈希表中的列表有一个很好的习惯用法:

(hash[key] ||= []) << new_value

现在,假设我编写了一个衍生散列类,就像在 Hashie 中找到的那样,它对我存储在其中的任何散列进行深度转换。那么我存储的将与我传递给 = 运算符的对象不同;Hash 可以转换为 Mash 或 Clash,并且可以复制数组。

这就是问题所在。Ruby 显然从 var= 方法返回传入的值,而不是存储的值。var= 方法返回什么并不重要。下面的代码演示了这一点:

class C
  attr_reader :foo
  def foo=(value)
    @foo = (value.is_a? Array) ? (value.clone) : value
  end
end

c=C.new
puts "assignment: #{(c.foo ||= []) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo ||= []) << 6}"
puts "c.foo is #{c.foo}"

输出是

assignment: [5]
c.foo is []
assignment: [6]
c.foo is [6]

当我将此作为错误发布给 Hashie 时,Danielle Sucher 解释了发生的事情并指出“foo.send :bar=, 1”返回 bar= 方法返回的值。(研究的提示!)所以我想我可以这样做:

c=C.new
puts "clunky assignment: #{(c.foo || c.send(:foo=, [])) << 5}"
puts "c.foo is #{c.foo}"
puts "assignment: #{(c.foo || c.send(:foo=, [])) << 6}"
puts "c.foo is #{c.foo}"

哪个打印

clunky assignment: [5]
c.foo is [5]
assignment: [5, 6]
c.foo is [5, 6]

有没有更优雅的方法来做到这一点?

4

3 回答 3

6

分配评估为正在分配的值。时期。

在其他一些语言中,赋值是语句,所以它们不会计算任何东西。这确实是仅有的两个明智的选择。要么不评估任何东西,要么评估被分配的值。其他一切都太令人惊讶了。

由于 Ruby 没有语句,因此实际上只有一种选择。

唯一的“解决方法”是:不要使用赋值。

于 2013-08-12T02:25:54.847 回答
5
c.foo ||= []  
c.foo << 5

使用两行代码并不是世界末日,而且看起来更容易。

于 2013-08-12T03:31:41.540 回答
1

最漂亮的方法是使用哈希的默认值:

# h = Hash.new { [] }
h = Hash.new { |h,k| h[k] = [] }

但请注意,您不能使用Hash.new([]),然后<<因为 Ruby 存储变量的方式:

h = Hash.new([])
h[:a] # => []
h[:b] # => []
h[:a] << 10
h[:b] # => [10] O.o

这是由于 Ruby 通过引用存储变量引起的,所以我们只创建了一个数组实例,将其设置为默认值,然后它将在所有哈希单元之间共享(除非它会被覆盖,即被覆盖h[:a] += [10])。

它是通过使用带有块(doc)的构造函数来解决的Hash.new { [] }。每次创建新键时都会调用这个块,每个值都是不同的数组。

编辑:修复了@Uri Agassi 所写的错误。

于 2013-08-12T05:40:54.120 回答