2009年11月9日月曜日

Ruby言語で好きなこと10項目

Yehuda Katzさんの「My 10 Favorite Things About the Ruby Language」という記事を取り上げたいと思います。

さっと考えるとクラスがオープンであること、全てがオブジェクトであること、文字列の表現が豊富であることの3つぐらいしか思いつきません。5つぐらいは挙げることができるように修行をしたいと思います。


Ruby言語で好きなこと10項目
Yehuda Katz

私は毎日Rubyを使って仕事をしています。日がたつにつれRubyを使うのが本当に楽しくなっています。Rubyについて本当に好きな項目の一覧を挙げます。何項目かは言うまでもないことだし、いくつかは他のプログラミング言語にも存在するようなことです。Rubyについて好きな項目を共有するのは他の言語と比較したり対比することではありません。

1. 動的な型付け

静的な型付けをするプログラミング言語にもいい点があります。たとえば、コンパイル時の検証が可能なことやIDEのサポートです。しかし、私の経験上、動的な型付けをするプログラミング言語は、プロジェクトを早く立ち上げる事に本当に役に立ち、特にプロジェクトの初期から中期にかけての変更を円滑にします。

後から他のクラスと簡単に取り替えることを可能なようにとりあえず実装している新しいオブジェクトのために形式的なインターフェース宣言を作る必要がないことがとてもうれしいです。

2. ダックタイピング

これは、事実上、単に動的な型付けの拡張です。Rubyでは、Stringオブジェクトで操作することができるであろうメソッドはis_a?(String)のようなチェックを行いません。それらのオブジェクトはそのオブジェクトがresond_to?(:to_str)するかどうか(to_strメソッドに反応するがどうか)をチェックして、もし反応するのであれば、そのObjectのto_strメソッドを呼び出します。同様にRubyでパスを表すオブジェクトは、パスの表現を提供するto_pathメソッドを実装することができます。

Railsでは、respond_to?(:to_model)が真である「モデル」の特性を持つオブジェクトにこの方法を使っています。これらのオブジェクトが自分自身の「モデル」表現を提供する時、この方法によって、関係のある状況においては、全てのオブジェクトをサポートすることができます。

3. すばらしいモジュール群

Rubyには、Scala、Squeak、そして、Perlでの「traits」のような機能を提供しています。効果的にRubyのモジュールを利用して、実行時にクラス階層へ新しい要素を動的に追加することができます。superメソッドを呼び出す時、追加されているかもしれないモジュールを考慮するために実行時に動的に評価します。クラス宣言時にsuperメソッドがどのクラスに対してのものかを無理に決めることなく、何回でも親クラスの機能を拡張することを容易にします。

さらには、Rubyのモジュールはappend_featuresやincludedメソッドのようなライフサイクルフックを提供します。これらのメソッドは、他のモジュールから拡張を分離するためや機能を含める基本となる動的にクラスを拡張するためにモジュールを確実に使うことを可能にします。

4. クラス宣言の中は特別ではないこと

Rubyでは、クラス宣言の中は特別なコンテキストではありません。selfがクラスオブジェクトを指している通常のコンテキストです。Railsを使ったことがあるなら、次のようなコードをおそらく見たことがあるでしょう:

class Comment < ActiveRecord::Base
  validates_presence_of :post_id
end
validates_presence_ofは言語の機能のように見えますが、実際には、ActiveRecord::Baseにより提供されているCommentクラスに対して呼び出されるメソッドです。 また、クラスのコンテキスト内で、そのメソッドは、新しいメソッドを作成したりなど、他のコード断片を実行したり、または、クラスのインスタンス変数を更新したりといった任意のコードを実行することができます。コンパイル時に実行されてしまうJavaのアノテーションのようではなく、Rubyのクラス宣言内では、動的に提供されるオプションや他のコードでの評価結果といった実行時の情報を利用することができます。 5. 文字列の評価 これはおそらく異端です。ここでは、任意で実行時に文字列を評価することではなく、Ruby製のアプリケーションのブートの初期にメソッドを作成するための文字列の評価のことです。 これによってRailsのルーティングやアスペクト指向プログラミングの定義のようにRubyで定義された構造をRubyのメソッドにコンパイルすることができます。もちろん、他の言語ではアドオンとしてこれらを実装することができますが、Rubyでは純粋にRubyでこのようなことを実装することができます。Rubyは他の言語と比べると大抵のことが可能な言語です。 6. ブロックとラムダ関数 ここで何回か言っていますが、もう一度くり前します: 無名ラムダ関数がない言語は日常に使用するのに機能不足と思います。これらの構成は実際かなり一般的で、Ruby、JavaScript、Scala、Clojure、そして、もちろん、Lispといった多様性のあるプログラミング言語に実装されています。 これらの機能は、言語のそのものの機能のようなブロックのスコープでの構成を実装することを可能にします。もっとも一般的な用例としてはFileクラスを使った操作です。ラムダ関数がない言語では、リソースを確実に処理するためにファイルを開いた同じ構文スコープでインラインの「ensure」ブロックを使わざるおえません。 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の場合、ファイルが閉じられることを保証するためにScannerオブジェクトの生成する部分をtryブロックで囲む必要があります。対照的にRubyでは:
def run(input)
  File.open(input, "r") do |f|
    f.each_line {|line| puts line }
  end
end
ブロックにより、1つの部分でファイルを閉じる必要性を抽象化することができます。これはプログラマによるエラーやコードの重複を少なくします。 7. 合わせ技で自己ホスト言語であること 上記で示した機能を組み合わせると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
この例では、新しいブロックスコープ構成を生成するために通常のRubyコード(ifやelse)でメソッド(respond_to)を途切れなく混ぜ合わせることができます。さらにコードブロックの境界と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メソッドはクラスメソッドとして提供されています。このメソッドは、 (indexメソッド内で)respond_withが、HTML、XML、または、JSONをレスポンスのフォーマットとして受け入れるべきだということをRailsに伝えています。もし、違うフォーマットを要求された場合は、自動的にHTTP406エラー(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::ClassMethodモジュールで定義されており、ActionController::Baseで使われます。加えて、mimes_for_respond_toメソッドは、モジュールのincludedメソッドによるフックを利用してのclass_inheritable_readerメソッドを利用して定義されています。class_inheritable_readerメソッド(マクロ?)は、組み込みのattr_accessorメソッドの機能をまねて該当するクラスへメソッドを追加するためにclass_evalメソッドを利用しています。 このような詳細を理解することは重要ではないです。重要なのは上記で示したようなRuby言語へ機能を追加しているように見える抽象化のレイヤーを作るとこを可能にする機能の使い方です。 ActionController::MimeRespondsを見る開発者はclass_inheriable_readerがどのように動作するかを理解する必要はなく、基本の機能を理解すればいいだけです。そして、API文書を見ている開発者はクラスレベルのrespond_toメソッドががどのように実装されているかを理解する必要はなく、提供されている機能を理解すればいいだけです。そんな訳で、それぞれのレイヤーを突き詰めることで、他の抽象化された層の上に構築されている簡単な抽象化の層へつながります。すべてを一度に突き詰める必要は全くありません。 8. よいリテラル Rubyでプログラムを書いているとこのことをよく忘れています。表現力の乏しいリテラルが少ししかないプログラミング言語を使っている際に思い出すだけです。 Rubyには全ての事柄に対応するようなリテラルがあります:
  • 文字列型: 単一行、複数行、行指向(ヒアドキュメント)
  • 数値型: 2進数、8進数、10進数、16進数
  • ヌル型: nil
  • ブーリアン型: 真、偽
  • 配列: [1,2], %w(each word is element)
  • ハッシュ: {key => value}やRuby 1.9では{key: value}という表現も
  • 正規表現: /hello/, %r{hello/path}, %r{hello#{interpolated}}
  • シンボル: :nameや:”weird string”
  • ブロック: { block literal }
列挙していないリテラルもあると思います。これは学術的なもしれませんが、豊富で読みやすいリテラルは、短くてもとても表現的なコードを書くというプログラマの能力を向上することができます。もちろん、例えば、新しいHashオブジェクトを生成して1度に1つずつキーと値を格納することによって、Hashリテラルで同じようなことを実現することができますが、メソッドパラメータとしての機能性を失ってしまいます。 Hashリテラルの簡潔さはRubyプログラマが言語設計者によって承認される必要なく言語に限定的なキーワード付き引数の機能を効果的に追加することができます。これはセルフホスティングであるということを示す他の例です。 9. 全てがオブジェクトであり、全てのコードが実行されて、selfがあること ある程度はこの点についてすでに言及していますが、クラスの宣言部がこのように動作することの主な理由は、Ruby言語の確かなオブジェクト指向の結果です。クラスの中で、Rubyは単にクラス自身を指しているselfを使ってコードを実行しているだけです。さらに、クラスのコンテキストはとくべつではありmせん。どんな場所からでも、クラスのコンテキストを評価することができます。次の例を考えてみて下さい:
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といった表現がかなり一般的です。Rubyではnilはただのオブジェクトであるため、理解しないメッセージを送ることはNoMethodErrorが発生します。開発者には次のような文法を勧めている人もいます: possibly_nil.try(:method_name)。これはRubyでは次のように実装することができます:
class Object
  alias_method :try, :__send__
end

class NilClass
  def try
    nil
  end
end
これは、基本的にtry目どっそを全てのオブジェクトに追加します。オブジェクトがnilである場合、tryメソッドは単にnilを返します。オブジェクトがnilでない場合、tryは当のメソッドを呼び出します。 Rubyでのクラスのオープン性の目標となる活用を使うことは、Rubyでは全てがnilを含めてオブジェクトであるということと組み合わせてオープンなクラスを利用することで、新しいRubyの機能を作り出すことが可能になりました。たいしたことではありませんが、言語の正しい設計が役立つ抽象性を作り出すという例です。 10. Rack RackはRuby言語の一部ではないので、ちょっとおかしいですが、RackからRubyの便利な機能を示します。はじめにRackライブラリは今年の初めにバージョン1.0がリリースされたばかりです。全てのRuby製のWebフレームワークは既にRack互換です。あなたが使おうとするRuby製のWebフレームワークはRackを間違いなく使っていますし、どんな標準のRackミドルウェアでも動作します。 後方互換性を失うことなくRackへの対応は行われました。Rubyの柔軟性のおかげです。 Rack自身も動作する上でRubyの機能を活用しています。RackのAPIは次のようです:
Rack::Builder.new do
  use Some::Middleware, param
  use Some::Other::Middleware
  run Application
end
この短いコード片で、いろいろな事が動作しています。最初にブロックがBack::Builderに渡されています。そして、2番目にそのブロックはRack::Builderの新しいインスタンスのコンテキスト内で評価されており、useやrunメソッドへアクセスできるようになっています。3番目にuseやrunメソッドに渡されるパラメータはクラス名です。それはRubyでは単にオブジェクトです。これによりRackはpassed_in_middleware.new(app, param)を呼び出すことができます。ここでのnewメソッドは、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でcallメソッドを定義します。このメソッドはRackに必要となり、環境変数を引数としてto_appメソッドを呼び出して、1連のミドルウェアを初期化します。 この記事で説明したいくつかの方法を使うことで、20行程度のコードでRackミドルウェアをサポートするRack互換のアプリケーションを作成することができました。

今回、この記事は自分にとってはとても難解でした。何となくは理解できていたのですが、日本語へ訳をする段階で、一般的なプログラミング言語の語彙や日本語での言い回しなどと色々と足りないことが多すぎることがよく分かりました。まずは一般的なプログラミング言語の語彙を増やしてみたいと思います。

0 件のコメント: