为啥符号不是冻结的字符串?
Posted
技术标签:
【中文标题】为啥符号不是冻结的字符串?【英文标题】:Why are symbols not frozen strings?为什么符号不是冻结的字符串? 【发布时间】:2012-06-20 13:59:44 【问题描述】:我了解字符串和符号之间的理论区别。我知道符号是用来表示概念或名称或标识符或标签或键的,而字符串是一袋字符。我知道字符串是可变的和瞬态的,而符号是不可变的和永久的。我什至喜欢在我的文本编辑器中 Symbols look 与 Strings 的不同之处。
困扰我的是,实际上,Symbol 与字符串非常相似,以至于它们没有实现为字符串这一事实引起了很多头痛。它们甚至不支持鸭式输入或隐式强制,不像其他著名的“相同但不同”的一对,Float 和 Fixnum。
当然,最大的问题是从其他地方进入 Ruby 的哈希值,如 JSON 和 HTTP CGI,使用字符串键,而不是符号键,因此 Ruby 程序必须向后弯曲才能将它们转换为前面或查找时间。 HashWithIndifferentAccess
的存在,以及它在 Rails 和其他框架中的猖獗使用,表明这里存在问题,需要解决的问题。
谁能告诉我为什么符号不应该被冻结字符串的实际原因?除了“因为它总是这样做”(历史)或“因为符号不是字符串”(乞求问题)之外。
考虑以下令人惊讶的行为:
:apple == "apple" #=> false, should be true
:apple.hash == "apple".hash #=> false, should be true
apples: 10["apples"] #=> nil, should be 10
"apples" => 10[:apples] #=> nil, should be 10
:apple.object_id == "apple".object_id #=> false, but that's actually fine
要让下一代 Ruby 主义者不那么困惑,只需这样做:
class Symbol < String
def initialize *args
super
self.freeze
end
(以及许多其他库级别的黑客攻击,但仍然不太复杂)
另见:
http://onestepback.org/index.cgi/Tech/Ruby/SymbolsAreNotImmutableStrings.red http://www.randomhacks.net/articles/2007/01/20/13-ways-of-looking-at-a-ruby-symbol Why does my code break when using a hash symbol, instead of a hash string? Why use symbols as hash keys in Ruby? What are symbols and how do we use them? Ruby Symbols vs Strings in Hashes Can't get the hang of symbols in Ruby http://blog.arkency.com/could-we-drop-symbols-from-ruby/ Do Ruby symbols exist because strings are mutable and not interned?更新:我认为 Matz 在这里很好地说明了 class Symbol < String
的情况:http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-core/9192(感谢 Azolo 挖掘这个问题,以及 Matz 最终撤回)。
【问题讨论】:
好问题!我记得当我开始学习 ruby 时被这个问题所困扰。但是随着时间的流逝,我只是忘记了它 +1 用于正确使用“乞求问题”(以及一个通常清晰且表达良好的问题)apples: 10["apples"]
应该是 10?所以1 => "foo"[1.0]
应该是"foo"
,因为您提到Fixnum
和Float
作为“正确”完成的类的示例?
我很困惑。在您的第一段中,您非常详细地解释了为什么符号和字符串根本不同,完全不同,彼此完全不同。然后,在第二段中,您声称它们几乎相同?
Jörg,它们的相似之处在于如果符号不存在,它们的功能几乎可以通过使用(冻结)字符串来完全复制。它们在语义上不同,但在功能上非常相似,大多数新手都惊讶于它们不相关。就像 javascript 没有整数类型(所有 JS 数字都是浮点数)一样,Ruby 也可以没有符号类型。再举一个例子,Stack 与 Array 不同,但 Matz 对 Array 添加 push 和 pop 没有问题;以同样的方式,一个字符串可以表现得像一个符号(并且没有额外的方法)。
【参考方案1】:
如果 String 可以继承 Symbol,因为它增加了很多功能(变异)。但是符号永远不能“作为”字符串使用,因为在所有需要突变的情况下它都会失败。
在任何情况下,正如我上面所说,string == symbol 绝不能像上面建议的那样返回 true。如果您对此稍加思考,您会注意到在考虑 子类 实例的类中不可能有合理的 == 实现。
【讨论】:
感谢您的回答,但这是一种乞求的问题。没有先验设计级别的原因为什么字符串不应该 == 到它们的符号等价物,就像浮点数现在 == 到它们的 fixnum 等价物一样。就像Matz notes here、Symbol < String
一样适用于 Smalltalk!【参考方案2】:
另一个考虑是"apple".each_char
有意义,但:apple.each_char
没有。字符串是“字符的有序列表”,而符号是没有明确值的原子数据点。
我想说HashWithIndifferentAccess
实际上表明 Ruby 符号正在履行两个不同的角色; symbols(本质上类似于其他语言中的枚举)和 interned strings(本质上是一种抢先优化,补偿了 ruby 被解释的事实,因此没有智能优化编译器的好处。)
【讨论】:
我猜这意味着它们可以被重新想象为< String
,以消除歧义。在这种情况下,“为什么不”的答案就是上面提到的 Azolo。【参考方案3】:
这个答案与我的original answer 截然不同,但我在 Ruby 邮件列表中遇到了一对interesting threads。 (两篇好书)
因此,在 2006 年的某一时刻,matz 将 Symbol
类实现为 Symbol < String
。然后 Symbol
类被剥离以消除任何可变性。所以Symbol
实际上是不可变的String
。
但是,它被还原了。 reason given was
尽管它非常反对 DuckTyping,但人们倾向于使用 case 在类上,而 Symbol
所以你的问题的答案仍然是:Symbol
就像 String
,但它不是。
问题不在于Symbol
不应该是String
,而是历史上不是。
【讨论】:
我发现了几个关于这个的邮件列表线程。改变了我的答案。 所以答案真的是纯历史的。我意识到人们需要修改他们的程序(例如,有时将if foo.is_a? String
更改为unless foo.is_a? Symbol
),但我没有想到case
语句的微妙之处。不过,这完全可以解决,只需通过排序(始终将when Symbol
放在when String
上方),这并不像一些 Ruby 陷阱那么可怕,而且我认为 case 语句通常比它的价值更麻烦...呃。
好吧,您总是可以游说更改它,但这意味着您必须说服人们帮助通过stdlib
并更改任何可能损坏的东西。另外我不知道YARV
的含义可能是什么。但仍然是一项高度政治化的任务。
这个问题已经出现在 bugs.ruby-lang.org 上:bugs.ruby-lang.org/issues/4801【参考方案4】:
它的基本原理是,这些不应该是真的:
:apple == "apple" #=> false, should be true
:apple.hash == "apple".hash #=> false, should be true
符号总是相同的对象,文本不是。
【讨论】:
这是在乞求问题。 "apple" == "apple" 是真的,即使它们是不同的实例,那么为什么不能 :apple == "apple" 呢? 为什么"1" == 1
不能是真的?它可以。这是某些语言做出的选择,但不是 Ruby 做出的选择。我认为在某种程度上这是同样的情况。 Matz 认为 Symbols 和 Strings 应该是两个不同的东西,所以它们是。
我反对让 :apple == "apple" 返回 true。 Symbol 和 String 不相同或不等价——即使两者之间存在继承关系,比较不同类型的两个实例是否等价也绝不能返回 true。
“比较两个不同类型的实例是否等价一定不能返回真”——反例参见(1.0 == 1)
这可能只是 Ruby 的一个糟糕的设计选择吗? HashWithIndifferentAccess imo 真的没有任何理由。【参考方案5】:
看到这个答案:https://***.com/a/6745253/324978
主要原因:性能(符号存储为整数,并且永远不会被垃圾回收)和一致性(:admin
和 :admin
将始终指向同一个对象,而 "admin"
和 "admin"
没有保证)等。
【讨论】:
在构造过程中可以优化性能并确保身份,就像现在解析器遇到符号时会查找符号表一样。我不是说符号不应该与字符串不同,我是说它们应该从字符串派生,因此更有用。【参考方案6】:我不知道完整的答案,但这里有很大一部分:
符号用于哈希键的原因之一是给定符号的每个实例都是完全相同的对象。这意味着:apple.id
将始终返回相同的值,即使您没有传递它。另一方面,"apple".id
每次都会返回不同的 id,因为创建了一个新的字符串对象。
这种差异就是为什么建议使用符号作为哈希键的原因。使用符号时无需进行对象等效性测试。它可以直接短路到对象标识。
【讨论】:
你是对的,解释器可以很容易地确保每个对 :apple 的引用都返回一个指向同一个实例的指针,即使该实例是 String 的子类。以上是关于为啥符号不是冻结的字符串?的主要内容,如果未能解决你的问题,请参考以下文章
为啥关键字参数必须作为带有符号键的哈希传递,而不是 Ruby 中的字符串键?
为啥在 ruby 中动态创建大量符号不是一个好主意(对于 2.2 之前的版本)?