【问题标题】:Strange behavior: Hash's keys cancel dynamic method definition奇怪的行为:哈希的键取消动态方法定义
【发布时间】:2025-12-04 09:35:01
【问题描述】:

假设我希望 String 的某些实例与其他“正常”实例的行为不同 - 例如取消“upcase”方法的效果。我执行以下操作:

class String
  def foo
    def self.upcase
      self
    end
    self
  end
end

它似乎工作正常,我需要它的方式:

puts "bar".upcase #=> "BAR"
puts "bar".foo.upcase #=> "bar"

但是,一旦我使用被欺骗的 String 实例作为 Hash 的键,我的行为就开始看起来很奇怪:

puts ({"bar".foo => "code"}).keys.first.upcase #=> "BAR", not "bar"!

... 相当于忽略了 foo 方法,将 String 的原始实例作为 key。

任何人都可以看到这里发生了什么?非常感谢!

【问题讨论】:

  • 我有一种强烈的感觉,对于您的实际问题,无论是什么,都有比这更好的解决方案。为什么你不希望这个字符串响应大写?
  • 我的问题可能足够具体,需要此解决方案,因为我使用 Ruby 生成 Javascript。在这种特殊情况下,我需要将 Hash 的键从下划线转换为驼峰式,以便生成的 JS 代码读起来自然。但是,在某些情况下,我只想让某些键完全按照我指定的方式保留,因此我添加了这个“修饰符”函数(上例中的“foo”,下例中的“preserve”),以取消转换,希望能够做到这一点: ({"converted_key" => 1, "passed_as_is".preserve => 1}).to_js #=> {"convertedKey" => 1, "passed_as_is" => 1}。我希望这是有道理的。
  • 嗯。因此,也许您可​​以将 Ruby 键的哈希值保留为 Javascript 键?然后,您会将您的特殊情况逻辑放入此哈希的创建中,因此 "this_one" => "ThisOne" 但 "this_special_one" => "this_special_one"。

标签: ruby hash metaprogramming


【解决方案1】:

Ruby 的 Hash 有一个使用字符串作为哈希键的特殊情况——它制作字符串的内部副本。

基本上它是为了防止您使用字符串(对象)作为键,然后在代码的后面更改该字符串对象,这可能会导致一些令人困惑的情况。可变键变得棘手。

我不会在返回更改后的字符串类的字符串上使用 hack 方法,而是创建一个新的字符串子类来覆盖大写,然后设置它的值。

【讨论】:

  • 我不认为创建一个子类会起作用。 “def self.foo”是用定义对象类的匿名子类实现的,这显然行不通,所以我认为 Ruby 会复制 s if s.kind_of?字符串,而不是 s.is_a?字符串。
  • 所以如果我向一个对象的实例添加一个函数,它会将该对象更改为之前的匿名子类吗?我没有意识到这一点。如果是这样的话,似乎你只剩下丑陋的解决方案了(除了,好吧,不要使用被破解的 String 版本作为键)
  • 不,Eli 的建议有效:gist.github.com/138566(请随意复制此代码,Eli)。大卫,def self.foo 影响对象的元类,并且不会改变类是什么。
【解决方案2】:

仅仅因为在 Ruby 中您可以重新打开核心类并几乎重新定义所有内容,但这并不意味着您应该这样做。

权力越大,责任越大,你的责任是不要仅仅因为几个对象可能需要它就重新定义核心库方法。 如果您的实例不像 Sting,请声明您自己的类并扩展 String。

【讨论】:

    【解决方案3】:

    在 Ruby 中扩展单个对象的常用方法是:

    s = "bar"
    class<<s
      def self.upcase
        self
      end
    end
    

    ...但这并不能解决您的问题。似乎 Ruby 对作为字符串或字符串子类的哈希键有特殊的规则 也许你可以使用一个有意义定义为 to_s 的对象来代替字符串?

    【讨论】:

      最近更新 更多