2013 年圣诞节发布的 Ruby 2.1 是 Ruby 的下一个重要版本,仅与2.0版本的发布有10个月的间隔。该版本包含了大量的更新和提升,这篇文章就来揭秘新特性的具体细节。
Ruby2.1改为了基于语义化版本控制 版本控制方案。
具体方案是MAJOR.MINOR.TEENY, 因此2.1.0中,主版本号是2, 次版本号是1,以及微版本号是0. 微版本号代表小Bug和安全补丁的修正程度。次版本号代表向后兼容的新特性,主版本号则是无法发布为次版本号的非兼容的更新。
这就意味着而不是说,曾经1.9.3大更新1.9.3-p545小更新变成了2.1大更新2.1.1小更新了。
计划每隔12个月释放一个次版本更新,因此2014年圣诞节我们应该可以看到Ruby2.2了。
Ruby 2.0.0中引入的 关键字参数 在 2.1中加了点小小的改进. 必须的关键字参数允许你在方法定义时删除关键字参数的默认值, 并且在方法被调用时,如果没有提供确切值则跑出异常。
# length is required def pad(num, length:, char: "0") num.to_s.rjust(length, char) end pad(42, length: 6) #=> "000042" pad(42) #=> #<ArgumentError: missing keyword: length>
像上面展示的例子中,我们可以看到关键字参数可以帮助我们消除哪个参数是哪个的歧义, 但是默认值并不总是必须的. 现在我们不必总是加默认值了。
Ruby中字符串是易变的,任何字符串文字在每次调用他们时都将产生一个新的字符串,例如
def env "development" end # returns new String object on each call env.object_id #=> 70329318373020 env.object_id #=> 70329318372900
这是十分浪费的,创建并垃圾回收了许多的对象。为了让你能够避免这种情况,在字符串文字上直接调用#freeze,这将导致在冻结字符串表中查找字符串。这就意味着同样的字符串将被再次使用。
def env "development".freeze end # returns the same String object on each call env.object_id #=> 70365553080120 env.object_id #=> 70365553080120
在Hash表中字符串文字作为键时,将被同样对待,但不需要调用#freeze
a = {"name" => "Arthur"} b = {"name" => "Ford"} # same String object used as key in both hashes a.keys.first.object_id #=> 70253124073040 b.keys.first.object_id #=> 70253124073040
在2.1开发期间,这个功能开始是作为一个附加语法,用"string"f导致一个冻结字符串。最终决定切换到在文字上调用#freeze的特定技术,这可以让代码向前和向后兼容,另外主观上很多人不喜欢新的语法。
定义一个方法的结果不在是nil, 取而代之的是方法名字的标识符。一个规范的例子是使得一个方法称为私有的。
class Client def initialize(host, port) # ... end private def do_request(method, path, body, **headers) # ... end def get(path, **headers) do_request(:get, path, nil, **headers) end end
同样它也提供了更好的添加方法修饰符的方式, 下面是一个使用 Module#prepend 来包装一个方法完成前/后调用。
module Around def around(method) prepend(Module.new do define_method(method) do |*args, &block| send(:"before_#{method}") if respond_to?(:"before_#{method}", true) result = super(*args, &block) send(:"after_#{method}") if respond_to?(:"after_#{method}", true) result end end) method end end class Example extend Around around def call puts "call" end def before_call puts "before" end def after_call puts "after" end end Example.new.call
输出
before call after
define_method和define_singleton_method方法也被更改为返回标识符而不是他们的参数。
我们已经有了整数(1) 和浮点数(1.0) 字面量, 现在我们也有有理数(1r)和复数(1i)字面量了。
他们配合Ruby的类型转换机制可以轻松搞定数学操作,好比,一个数学上的有理数1/3可以在Ruby中写作1/3r。3i会生成复数0+3i。这意味着复数可以写成标准的标准的数学符号, 2+3i 生成复数2+3i!
Ruby 2.0.0中许多类都有一个#to_h方法,现在数组和任何其他包含枚举的类也都有#to_h方法了。
[[:id, 42], [:name, "Arthur"]].to_h #=> {:id=>42, :name=>"Arthur"} require "set" Set[[:id, 42], [:name, "Arthur"]].to_h #=> {:id=>42, :name=>"Arthur"}
这将在所有Hash上返回数组的枚举方法中派上用场。
headers = {"Content-Length" => 42, "Content-Type" => "text/html"} headers.map {|k, v| [k.downcase, v]}.to_h #=> {"content_length" => 42, "content_type" => "text/html"}
Ruby2.1之前使用一个全局方法缓存,当代码中任何一个地方定义一个方法,模块引入,模块对象拓展时,这一全局方法缓存都会失效。这使得一些类--如OpenStruct -- 以及一些技术 -- 如exception tagging -- 出于性能原因而不可用。
现在不会有这个问题了, Ruby 2.1 使用基于类层次的方法缓存, 只有讨论中的类和任意子类才会失效缓存。
一个已经加入到 RubyVM 类的方法会返回一些方法缓存状态的调试信息。
class Foo end RubyVM.stat #=> {:global_method_state=>133, :global_constant_state=>820, :class_serial=>5689} # setting constant increments :global_constant_state Foo::Bar = "bar" RubyVM.stat(:global_constant_state) #=> 821 # defining instance method increments :class_serial class Foo def foo end end RubyVM.stat(:class_serial) #=> 5690 # defining global method increments :global_method_state def foo end RubyVM.stat(:global_method_state) #=> 134
现在异常有一个#cause方法,会返回造成的异常。当你从一个异常恢复并引发其他异常时,这个异常会被自动设置。
require "socket" module MyProject Error = Class.new(StandardError) NotFoundError = Class.new(Error) ConnectionError = Class.new(Error) def self.get(path) response = do_get(path) raise NotFoundError, "#{path} not found" if response.code == "404" response.body rescue Errno::ECONNREFUSED, SocketError => e raise ConnectionError end end begin MyProject.get("/example") rescue MyProject::Error => e e #=> #<MyProject::ConnectionError: MyProject::ConnectionError> e.cause #=> #<Errno::ECONNREFUSED: Connection refused - connect(2) for "example.com" port 80> end
目前引发的错误不会输出到任何地方,并且rescue不会关注原因。但是只有当调试时,自动设置异常原因才有些帮助。
Exceptions 也添加了#backtrace_locations方法 不知为啥2.0.0奇怪的消失了. 他返回Thread::Backtrace::Location 对象,而不是字符串,这更加方便访问调用栈信息了。
Ruby2.1引入了分代垃圾回收器,将所有的对象分到青年代和老年代。在标记阶段,只会对青年代运行一次常规的垃圾回收,因为较老的对象被标记的频率更低。1.9.3引入的延迟清理系统会执行清理工作。一个对象会被放入老年代如果它活过一次青年代垃圾回收。
如果老年代中有对象引用青年代中的对象, 但是你仅仅看到青年代似乎没有引用其他对象,并且你可能回收了一个正在使用的对象。写屏障可以通过对引用青年代对象的老年代对象(像old_arry.push(young_string))添加“记忆位”避免这一行为。当青年代标记阶段会考虑进这个“记忆位”。
大部分的分代垃圾回收器需要在所有对象上加上写屏障, 但是随着许多用于Ruby的第三方C拓展的出现使得这变得不可能, 因此一个变通方案是那些没有写屏障保护的对象(“阴暗”对象)将不会被放入老年代中。 这样并不理想,因为你不能体会到分代回收器的所有好处, 但是它最大化了向后兼容。
然而标记阶段是目前写屏障的最大开销,并且你的代码决定了你的性能。
GC.start方法有两个新的关键字参数,full_mark和immediate_sweep. 两个默认都是true。
full_mark如果设为true,则青年代和老年代都会被标记,否则只标记青年代。 immediate_sweep如果设为true,一次‘stop the world’的清理将会被执行,否则将会执行延迟清理, 直到被要求清理时,并且以最低要求进行清理。
GC.start # trigger a full GC run GC.start(full_mark: false) # only collect young generation GC.start(immediate_sweep: false) # mark only GC.start(full_mark: false, immediate_sweep: false) # minor GC
GC.stress调试选项现在可以设置一个integer的标志,来控制强调垃圾回收器的哪一部分。
GC.stress = true # full GC at every opportunity GC.stress = 1 # minor marking at every opportunity GC.stress = 2 # lazy sweep at every opportunity
GC.stat的输出已经被更新为包括更多细节,以及方法本身现在接受一个关键字参数来返回该关键字对应的值,而不是构建并返回完整的散列。
GC.stat #=> {:count=>6, ... } GC.stat(:major_gc_count) #=> 2 GC.stat(:minor_gc_count) #=> 4
GC 也添加了一个latest_gc_info方法,来返回最近垃圾回收的相关信息。
GC.latest_gc_info #=> {:major_by=>:oldgen, :gc_by=>:newobj, :have_finalizer=>false, :immediate_sweep=>false}本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务