Ruby创建者为什么选择使用符号的概念?


15

tl; dr:是否会有语言不可知的符号定义,以及是否有其他语言的符号?

那么,为什么Ruby创建者symbols在语言中使用的概念?

我是从非橄榄球程序员的角度提出这个问题的。我学习了许多其他语言,但没有发现需要指定我是否在处理Ruby所调用的语言symbols

主要的问题是, symbols Ruby存在以提高性能,还是仅仅因为语言的编写方式而需要?

Ruby中的程序会比Python或Javascript对应的程序轻和/或快吗?如果是这样,那是因为symbols吗?

由于Ruby的目的之一是易于人类阅读和编写,难道其创建者不能通过在解释器本身中实现这些改进来简化编码过程吗(就像其他语言一样)?

似乎每个人都只想知道symbols它们是什么以及如何使用它们,而不是为什么首先要知道它们的存在。


Scala在我头顶上有符号。我认为很多Lisps都会这样做。
D. Ben Knoble

Answers:


17

Ruby的创建者Yukihiro“ Matz” Matsumoto 发布了说明有关Ruby如何受到Lisp,Smalltalk,Perl的影响(维基百科也说Ada和Eiffel):

Ruby是一种按以下步骤设计的语言:

  • 采用简单的Lisp语言(例如CL之前的语言)。
  • 删除宏,s-表达式。
  • 添加简单的对象系统(比CLOS简单得多)。
  • 添加块,灵感来自高阶函数。
  • 添加在Smalltalk中找到的方法。
  • 添加在Perl中找到的功能(以OO方式)。

因此,从理论上讲,Ruby最初是Lisp。

从现在开始,我们将其称为MatzLisp。;-)

在任何编译器中,您都将管理函数,变量,命名块,类型等的标识符。通常,将它们存储在编译器中,而在生成的可执行文件中忽略它们,除非添加调试信息。

在Lisp中,此类符号是一流的资源,托管在不同的程序包中,这意味着您可以在运行时添加新的符号,并将其绑定到不同类型的对象。这在元编程时非常有用,因为您可以确定不会与代码的其他部分发生命名冲突。

同样,符号在读取时被插入,并可以通过标识进行比较,这是一种拥有新型(例如数字,但抽象)的有效方法。这有助于在直接使用符号值的地方编写代码,而不是定义由整数支持的自己的枚举类型。同样,每个符号可以保存其他数据。例如,这就是Emacs / Slime可以将Emacs中的元数据直接附加到符号的属性列表中的方式。

符号的概念在Lisp中很重要。请查看PAIP(人工智能编程范例:Norvig的Common Lisp中的案例研究)中的详细示例。


5
好答案。但是我不同意Matz:我永远不会想到将没有宏的语言称为lisp方言。Lisp的运行时元编程工具正是赋予该语言强大的功能的东西,弥补了其过于简单,缺乏表现力的语法。
cmaster-恢复莫妮卡

11

那么,为什么Ruby创建者必须symbols在语言中使用的概念?

好吧,他们没有严格地“必须”,而是选择了。另外,请注意,严格来讲Symbols不是语言的一部分,它们是核心库的一部分。它们确实具有语言级的文字语法,但是如果您必须通过调用来构造它们,它们也将同样有效Symbol::new

我从试图理解它的非橄榄球程序员的角度出发。我学习了许多其他语言,但没有一个语言需要指定我是否处理Ruby所调用的语言symbols

您没有说那些“许多其他语言”是什么,但是这里只是一些Symbol数据类型的摘录,这些数据具有像Ruby 这样的数据类型:

还有其他语言Symbol以不同的形式提供s 的功能。例如,在Java中,Ruby的功能String分为两种(实际上是三种)类型:StringStringBuilder/ StringBuffer。另一方面,Ruby的Symbol类型的特征被折叠成Java String类型:Java String可以被Interned,编译时求值常量表达式的结果的文字字符串Strings被自动插入,动态生成的Strings可以通过调用被插入的String.intern方法。一个被拘留StringJava中与SymbolRuby中的完全一样,但它不是作为单独的类型实现的,只是Java的不同状态。String可以在其中。(注意:在Ruby的早期版本中,该方法String#to_sym曾经被调用过,String#intern并且该方法今天仍作为旧别名存在。)

主要问题可能是:symbolsRuby中的概念是否作为其本身和其他语言的性能意图而存在,

Symbol首先是具有特定语义的数据类型。这些语义还使实现某些性能操作(例如快速O(1)相等性测试)成为可能,但这并不是主要目的。

还是仅仅是因为语言的编写方式而需要存在的东西?

Symbol根本不需要Ruby语言中的s,没有它们,Ruby也可以正常工作。它们纯粹是库功能。在语言中,与Symbols 紧密相关的只有一个地方:def方法定义表达式的计算结果Symbol表示所定义方法的名称。但是,这是一个相当新的变化,在此之前,未明确指定返回值。MRI仅评估为nil,Rubinius评估为一个Rubinius::CompiledMethod对象,依此类推。也可以将结果评估为UnboundMethod…或仅评估为String

Ruby中的程序会比Python或Node对应的程序轻和/或快吗?如果是这样,那是因为symbols吗?

我不确定您在这里问什么。性能主要取决于实现质量,而不是语言。另外,Node甚至不是一种语言,它是ECMAScript的事件I / O框架。在IronPython和MRI上运行等效的脚本,IronPython可能会更快。在CPython和JRuby + Truffle上运行等效的脚本,JRuby + Truffle可能会更快。这与无关Symbol s而与实现的质量有关:JRuby + Truffle具有积极优化的编译器,再加上高性能JVM的整个优化机制,CPython是一个简单的解释器。

由于Ruby的目的之一是易于人类阅读和编写,因此它的创建者难道不能通过在解释器本身中实现这些改进来简化编码过程吗(就像其他语言一样)?

编号Symbol不是编译器优化。它们是具有特定语义的单独数据类型。它们与YARV的flonums不同,后者是Floats 的私有内部优化。这种情况是不一样的IntegerBignum而且Fixnum,这应该是一种无形的私人内部优化的细节,但不幸的是,没有。(这最终将在Ruby 2.4中修复,该版本删除了FixnumBignum仅保留Integer。)

作为Java的特殊状态,以Java的方式进行操作String意味着您始终需要警惕Strings是否处于该特殊状态,以及在什么情况下它们会自动处于该特殊状态以及何时处于特殊状态。这比仅仅拥有一个单独的数据类型要高得多。

是否存在语言不可知的符号定义,以及将其用其他语言表达的理由?

Symbol是表示名称标签概念的数据类型。Symbols是值对象,是不可变的,通常是直接的(如果语言可以区分此类事物),无状态且没有身份。两个Symbol相等也保证是相同的,换句话说,两个Symbol S的相等实际上是相同的一个Symbol。这意味着值相等和引用相等是同一件事,因此相等有效且为O(1)。

使用某种语言使用它们的原因实际上是相同的,而与该语言无关。有些语言比其他语言更多地依赖它们。

例如,在Lisp系列中,没有“变量”的概念。相反,你有Symbol与关联的值。

在具有反射或内省能力的语言,Symbols的经常用来表示反映实体的名称在反射的API,如在Ruby中Object#methodsObject#singleton_methodsObject#public_methodsObject#protected_methods,和Object#public_methods返回ArraySymbolS(尽管他们可能只是以及返回ArrayMethod多个)。Object#public_send以一个Symbol表示要发送的消息的名称作为参数(尽管它也接受StringSymbol在语义上更正确)。

在ECMAScript中,Symbols是将来使ECMAScript功能安全的基本构建块。它们在反思中也起着重要作用。


Erlang原子直接来自Prolog(Robert Virding告诉我,在某些时候)
Zachary K

2

符号在Ruby中很有用,您会在Ruby代码中看到它们,因为每个符号在每次被引用时都会被重用。这是对字符串的性能改进,因为每次使用未保存在变量中的字符串都会在内存中创建一个新对象。例如,如果我多次使用相同的字符串作为哈希键:

my_hash = {"a" => 1, "b" => 2, "c" => 3}
100_000.times { |i| puts my_hash["a"] }

字符串“ a”在内存中创建了101,000次。如果我改用符号:

my_hash = {a: 1, b: 2, c: 3}
100_000.times { |i| puts my_hash[:a] }

符号 :a仍然是内存中的一个对象。这使得符号比字符串有效得多。

更新 以下是一个基准(摘自Codecademy),它演示了性能差异:

require 'benchmark'

string_AZ = Hash[("a".."z").to_a.zip((1..26).to_a)]
symbol_AZ = Hash[(:a..:z).to_a.zip((1..26).to_a)]

string_time = Benchmark.realtime do
  100_000.times { string_AZ["r"] }
end

symbol_time = Benchmark.realtime do
  100_000.times { symbol_AZ[:r] }
end

puts "String time: #{string_time} seconds."
puts "Symbol time: #{symbol_time} seconds."

这是我的MBP的结果:

String time: 0.1254125550040044 seconds.
Symbol time: 0.07360960397636518 seconds.

使用字符串和符号来识别散列中的键有明显的区别。


我不确定是否是这种情况。我希望Ruby实现可以多次执行相同的代码,而不是每次迭代都一次又一次地解析代码。即使每个词的出现"a"确实是一个新字符串,我认为在您的示例中也将恰好有两个"a"(并且实现甚至可以共享内存,直到其中一个被改变为止)。为了创建数百万个字符串,您可能需要使用String.new(“ a”)。但是我不太了解Ruby,所以也许我错了。
coredump

1
在Codecademy的一堂课中,它们为字符串和符号生成了基准,就像我的示例一样。我将其添加到答案中。
基思·马蒂克斯

1
感谢您添加基准。由于哈希表中的测试速度更快(身份与字符串比较),因此您的测试显示了使用符号而不是字符串获得的预期收益,但是我们无法推断出每次迭代都会分配字符串。我添加了一个版本, string_AZ[String.new("r")]以查看是否有所不同。我得到21ms的字符串(原始版本),7ms的符号和50ms的新字符串。因此,我要说的是,字面量"r"版本分配的字符串不多。
coredump

1
嗯,所以我做了更多的挖掘工作,在Ruby 2.1中,字符串实际上是共享的。我显然错过了该更新;感谢您指出了这一点。回到最初的问题,我认为两个基准测试都显示了符号和字符串的效用。
基思·马蒂克斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.