【51CTO精选译文】我每天都要用 Ruby 工作,久而久之,我现在真的喜欢上使用它了。(51CTO编者注:本文作者Yehuda Katz是Ruby on Rails核心开发团队的成员,以及Merb项目的主要推动者。)下面是一个列表,列出了我最喜欢的 Ruby 语言特点。一些特点显而易见,一些特点也存在于其他语言中。分享 Ruby 这些我喜欢的特点,并非是为了和其他语言进行比较和对比。
51CTO编辑推荐:Ruby入门教程与技巧大全
1. 动态类型
静态类型语言也有很不错的功能,比如编译时验证和 IDE 支持。不过根据我的经验,动态类型对于项目启动真的有很大帮助,并且便于进行更改,尤其是在项目的早期到中期这些阶段。
为了能够让我能够轻松地继续对象交换,我不需要为新对象创建正式的接口,这点让人很开心。
2. Duck Typing(鸭子类型)
这只是动态类型的一个有效的扩展。在 Ruby 中,预期能够对字符串对象进行操作的方法并不会检查 is_a?(String)。它们检查对象是否 respond_to?(:to_str),如果是,就接着调用对象的 to_str。与此类似,在 Ruby 中表示路径(Path)的对象能够实现一个 to_path 方法为提供路径重现(representation)。
在 Rails 语言中,对于具有“模型”特性的对象,我们可以使用这样的技巧来实现对它们 respond_to?(:to_model) 的预期。如果这些对象能够为我们提供一个它们自身的“模型”重现,我们就能够在相关语境中支持任何类型。51CTO之前曾发布过有关Ruby中鸭子类型的介绍,可以参考一二。
3. 令人叹为观止的模块
Ruby 提供了一个与 Scala、Squeak 和 Perl 语言中“traits”类似的功能。事实上,Ruby 模块可以在运行时动态地址类等级中添加新元素。运行时可以动态地对 super 的使用继续评估以考虑所有添加的模块,这样就可以方便地按照所需多次地扩展超类功能,而且无需指定在类声明时确定super的加载地点。
此外,Ruby 模块提供了生命周期钩子(hook)append_features 和 included,这样就可以使用模块来互相隔离扩展以及在特性包含的基础上动态的扩展类。
4. 类主体不是专用的
在 Ruby 中,类主体不是专用的语境。它们仅仅是一个对象类的自身指向点。如果你用过 Rails,你可能看到这样的代码:
- class Comment < ActiveRecord::Base
- validates_presence_of :post_id
- end
validates_presence_of 看起来好像是语言的一项功能,但实际上它是 Comment 上调用的方法,而 Comment 由 ActiveRecord::Base 提供。
该方法可以类中的执行任意代码(arbitrary code),包括创建新的方法,执行代码中其他内容,或者更新类实例变量。与必须在编译时运行的 Java 标注不同,Ruby 类主体能够考虑到运行时的信息,如动态提供的选项或其他代码的评估结果。
5. 字符串求值(eval 功能)
这可能是一个不同的想法。这里我不是指任意运行时字符串的求值,而是用于在 Ruby 程序启动过程中创建方法的字符串求值。
这样就能够利用 Ruby 定义的结构(如 Rails 路径或 AOP 定义),并且能够将其编译到 Ruby 方法中。当然,也可以将其作为其他语言的附件(add-on)来实现,但在纯 Ruby 环境中实现这类功能是可能的。在很大程度上,它是一种自足执行(self-hosting)的一种语言。
6. 区块和 Lambda 表达式
我已经说过多次,这里再重复一次:我认为没有匿名 lambda 表达式的语言还没有足够强大到让我每天使用它。这些构造事实上非常常见,在 Ruby、JavaScript、Scala、Clojure 和 Lisp 中也存在。51CTO之前介绍过Ruby 1.9中的Lambda表达式,有兴趣的读者可以看看。
利用它们,就能够实现看起来好像是语言功能的区块范围内的构造。最常见的使用示例是对文件的操作。在没有 lambda 的语言中,用户不得不在同一个语法范围(和他们最初打开的文件一致)中使用一段“确保”区块,以确保关闭了资源。
在 Java 中:
- static void run(String in)
- throws FileNotFoundException {
- File input = new File(in);
- String line; Scanner reader = null;
- try {
- reader = new Scanner(input);
- while(reader.hasNextLine()) {
- System.out.println(reader.nextLine());
- }
- } finally { reader.close(); }
- }
Java 版的代码常常需要在 try 区块中包括 Scanner 的创建以保证其关闭。相反,让我们看看 Ruby 的代码:
- def run(input)
- File.open(input, "r") do |f|
- f.each_line {|line| puts line }
- end
- end
由于区块的存在,我们可以省去在单个位置关闭文件的麻烦,将程序员错误减少到最小并且能够减少重复。
#p#
7. 功能组合:自足执行(self-hosting)语言
以上特点组合组合在一起,让我们能够在 Rails 中“扩展”Ruby 语言。看下面这段代码:
- respond_to do |format|
- if @user.save
- flash[:notice] = 'User was successfully created.'
- format.html { redirect_to(@user) }
- format.xml { render :xml => @user, :status =>ted, :location => @user }
- else
- format.html { render :action => "new" }
- format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
- end
- end
在这个示例中,我们可以无缝地将方法(respond_to)与正常的 Ruby 代码(if 和 else))混合在一起,以生成一个新的区块范围的构造。Ruby 的区块语法让我们能够在区块内使用 return 和 yield,从而进一步混合代码区块与语言构造(如 if 或while 的界限)。
在 Rails 3 中,我们引入下面一段代码:
- class PeopleController < ApplicationController
- respond_to :html, :xml, :json
- def index
- @people = Person.find(:all)
- respond_with(@people)
- end
- end
这里,我们在类中提供 respond_to。它告诉 Rails:respond_with(在 index 中)应接收 HTML、XML、或 JSON 作为响应格式。如果用户请求不同的格式,我们将自动返回一个 406 错误(Not Acceptable)。
如果再稍微深入挖掘一下,你会看到 respond_to 方法被定义为:
- def respond_to(*mimes)
- options = mimes.extract_options!
- only_actions = Array(options.delete(:only))
- except_actions = Array(options.delete(:except))
- mimes.each do |mime|
- mime = mime.to_sym
- mimes_for_respond_to[mime] = {}
- mimes_for_respond_to[mime][:only] = only_actions unless only_actions.empty?
- mimes_for_respond_to[mime][:except] = except_actions unless except_actions.empty?
- end
- end
这个方法在 ActionController::MimeResponds::ClassMethods 模块上定义,而该模块属于 ActionController::Base。此外,在该模块的生命周期钩子 included 中使用 class_inheritable_reader 定义了 mimes_for_respond_to。class_inheritable_reader method (macro?)。 使用 class_eval 将方法添加到正在使用的类上,以模拟内置的 attr_accessor 功能。
是否理解所有这些细节无关紧要。重要的是利用上述的 Ruby 功能,我们就可以创建抽象层,从而能够为 Ruby 语言添加新的特性。
开发者看到 ActionController::MimeResponds,他无需去了解 class_inheritable_reader 如何运行——他只需了解这个基本功能。而看到 API 文档的开发者也无需了解 class-levelrespond_to 如何运行——他只需了解这个已经提供的功能。
这样,剥离每一层就可以在其他抽象上构造一个简单的抽象。没有必要一次剥离所有抽象层。
8. 很好的字面含义
在使用 Ruby 编程时,我常常会忘记这一点;只有在使用一些字面意义很少或表达很差的语言时,我才会体会到 Ruby 的这一优点。
Ruby 中每个词都具有很好的字面意义:
- 字符串:single-line、double-line、interpolated
- 数字: binary、octal、decimal、hex
- 空值:nil
- 布尔玛:true、false
- 数组: [1,2], %w(每个字都是元素)
- 哈希表(Hash): {key => value} 和{key: value}(Ruby 1.9)
- 正则表达式:/hello/、%r{hello/path}、%r{hello#{interpolated}}
- 符号::name 和 :”weird string”
- 区块:{ 区块文字 }
我想我可能会漏掉一些。虽然有些学术性,但可读性良好的语句的确能够增强开发者的编码能力,让他们写出简短而***表达性的代码。
当然,通过对新的 Hash 对象实例化并一个一个地输入关键字和值,你也可以实现 Hash 的功能。但这减少了 Hash 的用途,比如作为方法参数。
Hash 字面上的简洁性让 Ruby 程序员能够无需经过语言设计者的许可就能够添加限制性关键字参数。这也是自足执行的又一个实例。
9. 所有事物都是对象,所有代码都是可执行的并具有 self
很大程度上,类主体之所以能够按照这样的方式运行,是 Ruby 语言始终如一地面向对象的结果。在类主体内部,Ruby 仅执行具有指向类的 self 的代码。此外,类内容中没有什么是专用的;可以在任何位置对类语境中的代码进行求值。
比如:
- module Util
- def self.evaluate(klass)
- klass.class_eval do
- def hello
- puts "#{self} says Hello!"
- end
- end
- end
- end
- class PersonName < String
- Util.evaluate(self)
- end
这完全等同于:
- class PersonName < String
- def hello
- puts "#{self} says Hello!"
- end
- end
Ruby 移除了不同位置代码之间的人工界限,降低了创建抽象的概念上的成本。这是强大的、始终如一的对象建模的结果。
有关该主体,再提供一个示例。Ruby 常见的术语:possibly_nil && possibly_nil.method_name。由于 nil 只是 Ruby 的一个对象,向它发送一个它无法理解的信息,会造成一个 NoMethodError 错误。有些开发者建议使用这种句法:possibly_nil.try(:method_name)。可以在 Ruby 中通过以下代码来实现:
- class Object
- alias_method :try, :__send__
- end
- class NilClass
- def try
- nil
- end
- end
本质上,这将为每个对象添加方法 try。当 Object 是 nil 时,try 只返回 nil。但 Object 不是 nil 时,try 就调用当前所用的方法。
使用 Ruby 开放类的目标程序,结合 Ruby 中所有事物都是对象(包括 nil)这一事实,我们就能够创建新的 Ruby 功能。同样,这没有什么大不了的,不过是又一个示例:在语言中做出正确的选择,我们就能够创建有用的抽象。
#p#
10. Rack
由于 Rack 不是 Ruby 语言的组成部分,所以这一点有点欺骗性。但是,它的确可以演示某些有用的功能。首先,今年早些时候,Rack 库才发布 1.0,并且所有单个 Ruby web 框架都已经与 Rack 兼容。如果你使用 Ruby 框架,我保证你就可以使用 Rack,并且所有标准的 Rack 中间件也可以运行。
做到这一点无需牺牲任何向后的兼容性,这也说明了 Ruby 语言的灵活。Rack 本身也可以利用 Ruby 功能来完成工作。
Rack API 如下:
- Rack::Builder.new do
- use Some::Middleware, param
- use Some::Other::Middleware
- run Application
- end
在这个简短的代码片段中,包含了很多东西。首先,一个区块被传递到 Rack::Builder。第二,该区块在一个新的 Rack::Builder 实例(通过它可以访问 use 和 run 方法)中进行求值。第三,传递到 use 和 run 的参数是类的名字,在 Ruby 中它是一个简单的对象。这样,Rack 就能够调用 passed_in_middleware.new(app, param),其中 new 是一个调用 Class 对象 Some::Middleware 的方法。
假如你认为上面的实现可能会需要一堆你所憎恶的代码,让我们再看下面:
- class Rack::Builder
- def initialize(&block)
- @ins = []
- instance_eval(&block) if block_given?
- end
- def use(middleware, *args, &block)
- @ins << lambda { |app| middleware.new(app, *args, &block) }
- end
- def run(app)
- @ins << app #lambda { |nothing| app }
- end
- end
上面我演示创建了一个新的 Rack 程序,这里就是所需的所有代码。对中间件链进行实例化也很简单:
- def to_app
- inner_app = @ins.last
- @ins[0...-1].reverse_each { |app| inner_app = app.call(inner_app) }
- inner_app
- end
- def call(env)
- to_app.call(env)
- end
首先,我们从该链中取出***一个元素(末点),然后我们以相反的方向遍历其余元素,使用链中的下一个元素对每个中间件进行实例化,并返回结果对象。
***,我们在 Builder 上定义了一个调用方法(Rack 尤其要求),它调用 to_app 并将环境传递过去,结束这个链。
通过本文中描述的这些技巧,利用几十行的代码,我们就能够创建支持 Rack 中间件、兼容 Rack 的应用程序。
原文:My 10 Favorite Things About the Ruby Language
作者:Yehuda Katz
【编辑推荐】