为什么模块调用的块不能修改在 Ruby 中实现 类 的对象?
Why a block invoked by a Module can't modify objects from implementing classes in Ruby?
我有一些数据保存在深度嵌套的哈希和数组中,我 运行 在数据的文本编码方面遇到了麻烦。我确实知道文本是用 "UTF-8" 编码的,所以我决定遍历每个元素并强制编码。
因此,我为 Enumerable
模块创建了一个名为 deep_each
的方法:
module Enumerable
def deep_each(&block)
self.each do |element|
if element.is_a? Enumerable then
element.deep_each(&block)
else
block[element]
end
end
end
end
并希望能够使用以下方法调用修复数据:
deephash.deep_each {|element| element.force_encoding("UTF-8") if element.class == String}
但结果令人失望:
deephash.deep_each {|element| element.force_encoding("UTF-8") if element.class == String}
> RuntimeError: can't modify frozen String
> from (pry):16:in `force_encoding'
然后我将函数向下移动到层次结构 "Array" 和 "Hash" 类:
class Hash
def deep_each(&block)
self.each do |element|
if [Array, Hash].include? element.class then
element.deep_each(&block)
else
block[element]
end
end
end
end
class Array
def deep_each(&block)
self.each do |element|
if [Array, Hash].include? element.class then
element.deep_each(&block)
else
block[element]
end
end
end
end
令人惊讶的是,同样的调用现在有效了。
我在这里违反了什么约束,如何为所有 Enumerables 定义一个方法而不为每个 Enumerables 定义它?
据我所知,您的 Enumerable 版本和 Array/Hash 猴子补丁应该得到完全相同的错误。我愿意。您确定在这两种情况下都使用相同的 deephash
吗?
通常当您在散列上循环 each
时,您会将键和值都传递给块。您正在将单个值 element
传递给块。这是一个包含键和值的数组:
irb> {a:1, b:2}.each {|el| puts el.inspect }
[:a, 1]
[:b, 2]
您的 deep_each
检查这是否是一个 Enumerable,确实是,所以它在列表中调用 deep_each
。然后,最后,您到达叶子并调用键和值上的块。该块检查它是否正在使用字符串,如果是,则强制编码。
如果您的哈希键是一个字符串,您将尝试对其进行变异。但是散列键被冻结,因此 RuntimeError: can't modify frozen String
被引发。
irb> {a: {b: {c: "abc"}}}.deep_each { |el| el << "efg" if String === el}
=> {:a=>{:b=>{:c=>{:d=>"abcefg"}}}}
irb> {a: {b: {"c" => "abc"}}}.deep_each { |el| el << "efg" if String === el}
RuntimeError: can't modify frozen String
str = "\xE2\x82\xAC" #Euro sign in UTF-8
puts str.encoding #=> UTF-8
puts str #=> Euro sign in a UTF-8 enabled terminal window
File.open('data.txt', 'w:utf-8') do |f|
f.write("#{str}\n")
end
Encoding.default_external = 'ISO-8859-1'
str = File.read('data.txt')
puts str.encoding #=> ISO-8859-1
arr = [
{a: str},
{b: 'world'},
]
arr[0][:a].force_encoding('utf-8')
puts arr[0][:a].encoding #=> UTF-8
puts arr[0][:a] #=> Euro sign in a UTF-8 enabled terminal window
如果您张贴以下示例会更说明问题:我 运行 遇到了数据文本编码方面的问题
Finally, it looks like writing the method for each class separately
makes more sense. For the Hash I need to use each_value rather than
each
你可以这样做:
iterator_for = Hash.new(:each) #When a non-existent key is looked up, return :each
iterator_for.update({
Hash => :each_value,
})
data = [
%w{ hello world goodbye },
{"a" => "red", "b" => "blue"},
]
data.each do |element|
element.send(iterator_for[element.class]) do |x|
puts x
end
puts '-' * 20
end
--output:--
hello
world
goodbye
--------------------
red
blue
--------------------
我有一些数据保存在深度嵌套的哈希和数组中,我 运行 在数据的文本编码方面遇到了麻烦。我确实知道文本是用 "UTF-8" 编码的,所以我决定遍历每个元素并强制编码。
因此,我为 Enumerable
模块创建了一个名为 deep_each
的方法:
module Enumerable
def deep_each(&block)
self.each do |element|
if element.is_a? Enumerable then
element.deep_each(&block)
else
block[element]
end
end
end
end
并希望能够使用以下方法调用修复数据:
deephash.deep_each {|element| element.force_encoding("UTF-8") if element.class == String}
但结果令人失望:
deephash.deep_each {|element| element.force_encoding("UTF-8") if element.class == String}
> RuntimeError: can't modify frozen String
> from (pry):16:in `force_encoding'
然后我将函数向下移动到层次结构 "Array" 和 "Hash" 类:
class Hash
def deep_each(&block)
self.each do |element|
if [Array, Hash].include? element.class then
element.deep_each(&block)
else
block[element]
end
end
end
end
class Array
def deep_each(&block)
self.each do |element|
if [Array, Hash].include? element.class then
element.deep_each(&block)
else
block[element]
end
end
end
end
令人惊讶的是,同样的调用现在有效了。
我在这里违反了什么约束,如何为所有 Enumerables 定义一个方法而不为每个 Enumerables 定义它?
据我所知,您的 Enumerable 版本和 Array/Hash 猴子补丁应该得到完全相同的错误。我愿意。您确定在这两种情况下都使用相同的 deephash
吗?
通常当您在散列上循环 each
时,您会将键和值都传递给块。您正在将单个值 element
传递给块。这是一个包含键和值的数组:
irb> {a:1, b:2}.each {|el| puts el.inspect }
[:a, 1]
[:b, 2]
您的 deep_each
检查这是否是一个 Enumerable,确实是,所以它在列表中调用 deep_each
。然后,最后,您到达叶子并调用键和值上的块。该块检查它是否正在使用字符串,如果是,则强制编码。
如果您的哈希键是一个字符串,您将尝试对其进行变异。但是散列键被冻结,因此 RuntimeError: can't modify frozen String
被引发。
irb> {a: {b: {c: "abc"}}}.deep_each { |el| el << "efg" if String === el}
=> {:a=>{:b=>{:c=>{:d=>"abcefg"}}}}
irb> {a: {b: {"c" => "abc"}}}.deep_each { |el| el << "efg" if String === el}
RuntimeError: can't modify frozen String
str = "\xE2\x82\xAC" #Euro sign in UTF-8
puts str.encoding #=> UTF-8
puts str #=> Euro sign in a UTF-8 enabled terminal window
File.open('data.txt', 'w:utf-8') do |f|
f.write("#{str}\n")
end
Encoding.default_external = 'ISO-8859-1'
str = File.read('data.txt')
puts str.encoding #=> ISO-8859-1
arr = [
{a: str},
{b: 'world'},
]
arr[0][:a].force_encoding('utf-8')
puts arr[0][:a].encoding #=> UTF-8
puts arr[0][:a] #=> Euro sign in a UTF-8 enabled terminal window
如果您张贴以下示例会更说明问题:我 运行 遇到了数据文本编码方面的问题
Finally, it looks like writing the method for each class separately makes more sense. For the Hash I need to use each_value rather than each
你可以这样做:
iterator_for = Hash.new(:each) #When a non-existent key is looked up, return :each
iterator_for.update({
Hash => :each_value,
})
data = [
%w{ hello world goodbye },
{"a" => "red", "b" => "blue"},
]
data.each do |element|
element.send(iterator_for[element.class]) do |x|
puts x
end
puts '-' * 20
end
--output:--
hello
world
goodbye
--------------------
red
blue
--------------------