牛人點評Ruby語言十大令人喜愛的特點
譯文【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
【編輯推薦】