Hatena::Diary

はてなダイアリー - 無料で簡単。広告のないシンプルなブログをはじめよう!

2005-08-16

RubyについてJavaプログラマが知るべき10の事柄

原文:10 Things Every Java Programmer Should Know About Ruby

Jim Weirich

コンサルタント / Compuware

jim@weirichhouse.org


f:id:brazil:20050816051001g:image:w200

Copyright 2005 by Jim Weirich (All Rights Reserved)

自己紹介

  • ジム・ワイリック (Jim Weirich)
  • Compuware社コンサルタント
  • Javaプログラマ
  • Rubyの熱狂的支持者

大事なことを、まず先に

私は以前、大規模製造会社の従業員に就業後のC言語コースを教えていました。提出されたCのコードのスタイルをちょっと見ただけで、生徒が以前にどんな言語を利用していたかを簡単に当てられました。全くもって本当のことです。「どの言語でも、君はFORTRANを書けるね」


Rubyを調査中のJavaプログラマなら、Rubyが色々な点でJavaに似ていることに気づくはずです。クラスがあり、モジュールがあり、ネームスペース、スコープ、インスタンス変数、あとメソッドもあります。このためJavaプログラマは、このオブジェクト指向言語はお手のものだと感じるかもしれません。


そして、そのままJavaのスタイルでプログラムを作成し続けたくなることでしょう。

もちろん異なる点もいくつかは存在します。(たぶん最初の衝撃は、型宣言がないことです)

でも少し努力すればなんとかなります。さもなければ絶好の機会を見失ってしまうことになるでしょう。

サピア・ウォーフの仮説

サピア・ウォーフの仮説は、「思考と行為は言語により決定される(少なくとも部分的には)」という理論です。 […] 今日まで、議論は決着していませんし、完全に証明されてもいません。しかし、世界中の研究者の興味を集め続けています。


引用

プログラムについての考察を与えない言語は知る価値が無い

―Alan Perlis

The Ruby Way

この記事は「RubyJavaより素晴らしい」という類のものではありません。

Javaには「これもない」「あれもない」という論法は、JavaJavaプログラマを強く非難することになりがちです。

それよりも「RubyJavaを書く」という状態を乗り越えるのを助け、Rubyを調査中のJavaプログラマRuby Wayを発見することの一助になればと思っています。


では早速、10の事柄を...

アイテム #10 Rubyの慣習を学ぶ

  • ClassNames (クラス名)
  • method_names and variable_names (メソッド名と変数名)
  • methods_asking_a_question? (真偽値を返すメソッド)
  • slightly_dangerous_methods! (レシーバの状態を変化させるメソッド)
  • @instance_variables (インスタンス変数)
  • $global_variables (グローバル変数)
  • SOME_CONSTANTS or OtherConstants (定数)

いくつかの慣習は言語自体で規定されています。その他はコミュニティで標準に使われているだけです。

アイテム #9 全てがオブジェクト

Rubyでは、変数名へ割り当てられるもの全てが完全なオブジェクトです。


これは面白い効果があります。

クラスはオブジェクト!

  a = Array.new

ファクトリが僅かになる

クラスは自らインスタンスを作成するため、究極のファクトリオブジェクトと言えます。

  def create_from_factory(factory)
    factory.new
  end

  obj = create_from_factory(Array)

プリミティブ型がない!

整数さえも完全なオブジェクトです。

  0.zero?    # => true
  1.zero?    # => false
  1.abs      # => 1
  -1.abs     # => 1
  1.methods  # => 1オブジェクトのメソッドのリスト
  2.+(3)     # => 5  (2+3と同じ)
  10.class   # => Fixnum
  (10**100).class
             # => Bignum

nilオブジェクト!

  a = nil
  a.nil?     # => true
  a.methods  # => メソッドのリスト
  a.abs      # => NoMethodError(メソッドが存在しないエラー)

オブジェクトではないもの


    • これに対する回避策はあります。
      • しかし、それは高度なトピックです。
      • いずれにせよ本当に必要とする人はいないでしょう。

その他のオブジェクトでないもの

  • ブロックはオブジェクトではありません。
    • しかし、そこに大差はありません。
    • それを必要とする時までには、自動的にProcオブジェクトに変換されるからです。
  def with_block
    yield
  end

  with_block {
    # 決して変換されない
  }
  def with_proc(&block)
    block.call
  end

  with_proc {
    # 内部的に変換される
  }

アイテム #8 (ほとんど) 全てがメッセージ

Rubyの全ての処理は以下を通じて実行されます:

これら全てがメッセージ...

string.index("x")
送信 :index ("x"を引数として)
string.length
送信 :length (引数無しで)
run_status_reports
送信 :run_status_reports (自分自身(self)へ)
1 + 2
送信 :+ (2を引数として) オブジェクト1へ
array[i]
送信 :[] (iを引数として) array へ

メッセージは関数呼び出しではない

Javaプログラマはobj.method()を、「メンバ関数の一覧から関数を探してきて、それを実行する」と考えがちです。


Rubyプログラマはobj.methodを、「メッセージをオブジェクトへ送る」というように考えます。


違いは何か?

違いは僅かですが重要です!

どんな違いがあるのでしょう?

以下のようなクラスを考えてください。ここでは今まで送られたすべてのメッセージを記録できるオブジェクトを定義しています。そして、それらのメッセージを別のオブジェクトへ再度送信することもできます。

  class VCR
    def initialize
      @messages = []
    end
    
    def method_missing(method, *args, &block)
      @messages << [method, args, block]
    end
    
    def play_back_to(obj)
      @messages.each do |method, args, block|
        obj.send(method, *args, &block)
      end
    end
  end
メソッド呼び出し記録の再生

サンプルコード

  require 'src/vcr'

  vcr = VCR.new
  vcr.sub!(/Java/) { "Ruby" }
  vcr.upcase!
  vcr[11,5] = "Universe"
  vcr << "!"

  string = "Hello Java World"
  puts string

  vcr.play_back_to(string)
  puts string

出力

  Hello Java World
  HELLO RUBY Universe!

メッセージの有効な利用場面...

リモートプロキシ
全てのメッセージを自動的にリモート・オブジェクトへ転送します。
自動ローダ
最初のメッセージを受け取るまで、オブジェクトの代わりになります。ロードされると、通常のプロキシとして振舞います。これはデータベースと連携するオブジェクトに最適でしょう。
デコレータ
必要なメッセージを捕捉し、不要なメッセージをパスします。
モックオブジェクト
偽装する必要があるメソッドを書くだけです。後は必要に応じて、代理応答するか無視するかを選べます。
ビルダ
ビルダーの呼ばれたメソッドを元に、XML/HTML/その他なんでも作ることができます。

アイテム #7 Ruby は想像以上にダイナミック

C++に比べた時のJavaの大きな魅力の1つは、言語の動的な側面です。実行時にクラスをロードし、オブジェクトの持つクラスやメソッドを調べて、その時に発見されたメソッドを呼ぶこともできます。

Javaより遥かにダイナミック

RubyJavaよりも幾つかの点でダイナミックに動作します。

  • method_missing (存在しないメソッドの捕捉)
  • 簡単なリフレクション
  • 開放的なクラス
  • シングルトンオブジェクト
  • 定義へのフック
  • コードの評価

簡単なリフレクション: オブジェクトの作成

Javaバージョン

  public static Object create(Class c, String value)
    throws Exception
  {
    Constructor ctor = c.getConstructor(
      new Class[] { String.class } );
    return ctor.newInstance(
      new Object[] { "Hello" } );
  }

  public static void main (String args[])
    throws Exception
  {
    Greeting g =
      (Greeting) create(Greeting.class, "Hello");
    g.show();  
  }

簡単なリフレクション: オブジェクトの作成

Rubyバージョン

  def create(klass, value)
    klass.new(value)
  end

  create(Greeting, "Hello")

開放的なクラス

クラスの好きな場所にメソッドを追加できます。これはビルトインクラスに対しても同様です。

  class Integer
    def even?
      (self % 2) == 0
    end
  end

  p (1..10).select { |n| n.even? }
    # => [2, 4, 6, 8, 10]

注意が通知されますが、この機能は非常に便利です。

特異メソッド

特異メソッドはクラスではなく、個々のオブジェクトに定義されます。

  class Dog
  end

  rover = Dog.new
  fido = Dog.new

  def rover.speak
    puts "Red Rover"
  end

  rover.speak  # => "Red Rover"
  fido.speak   # => NoMethodError

フック

フックにより、プログラム実行時の気になる瞬間をプログラマがコントロールすることができます。


  class MyClass
    def MyClass.method_added(name)
      puts "Adding Method #{name}"
    end

    def new_method
      # 省略
    end
  end

出力

  Adding Method new_method

コードの評価

  class Module
    def trace_attr(sym)
      self.module_eval %{
        def #{sym}
          printf "Accessing %s with value %s\n",
          "#{sym}", @#{sym}.inspect
          @#{sym}
        end
      }
    end
  end
  
  class Dog
    trace_attr :name
    def initialize(string)
      @name = string
    end
  end
  
  Dog.new("Fido").name  # => Accessing name with value "Fido"

アイテム #6 オブジェクトは強く型付けされるが、静的型付けではない

型とは何か?

型とは

 「値の集合」

 「操作の集合」


Cのコード (弱い型付け)

  #include <stdio.h>
  extern float two();
  int main() {
      float x = 1.5 + two();
      printf("%f\n", x);
      printf("%d\n", x);
      return 0;
  }
int two() { return 2; }

出力

nan
0

Javaのコード (強い型付け)

  public class Main {
    public static
      void main (String args[]) {
      double x = 1.5 + Two.two();
      System.out.println(x);
    }
  }
  public class Two {
    public static int two() {
      return 2;
    }
  }

出力

3.5

Rubyのコード (?)

  require 'two'

  x = 1.5 + two
  puts x
  printf "%d", x
  def two
    2
  end

出力

  3.5
  3

どうすれば言語の型を安全にできるか?

または...

  • 以下のどちらかで、全ての不正な操作をキャッチする

Rubyのコード

  def factorial(n)
    result = 1
    (2..n).each do |i|
      result *= i
    end
    result
  end

  puts factorial(20)
  puts factorial(21)

出力

  2432902008176640000
  51090942171709440000

Javaのコード

  public class Fact {
    static long factorial(long n) {
      long result = 1;
      for (long i=2; i<=n; i++)
        result *= i;
      return result;
    }
    
    public static
      void main (String args[]) {
      System.out.println(factorial(20));
      System.out.println(factorial(21));
    }
  }

出力

  2432902008176640000
  -4249290049419214848

言語の型付けの機構

JavaRuby
強い型付け強い型付け
静的動的
明示的暗黙的

証言

Bob Martin (http://www.artima.com/weblogs/viewpost.jsp?thread=4639)

長い間私は、頑固な静的な型付け支持者でした。Cを使っていた頃に、身をもって教訓を得ていきました。たくさんのシステムが、ばかげた型エラーのせいで本稼動中にクラッシュしました。 […]

4年前に、XP(Extreme Programming)に関わりました。[…]今では、開発をバックアップする包括的な単体テスト一式がないプロジェクトは想像できません。

[…]

2年程前に、私はあることに気が付きました。それは、私がどんどん安全のために存在する型付けの機構を当てにしなくなっていたことです。単体テストが型エラーの発生を防いでいてくれていたためです。[…]

そしてPythonアプリケーションをいくつか書き、続いてRubyでも書いてみました。型に関する問題が全く発生しないことがわかったときも、全然驚きませんでしたよ。

アイテム #5 インターフェイスに関する心配は無用

RubyはDuck Typingを使います。

  • もしアヒルのように歩き、
  • アヒルのように鳴くのなら、
    • それはアヒルとして扱える
    • (それが本当は何かを気にする人はいないでしょう)
  class Duck
    def talk() puts "Quack" end
  end
  class DuckLikeObject
    def talk() puts "Kwak" end
  end
  flock = [
    Duck.new,
    DuckLikeObject.new ]
  flock.each do |d| d.talk end

一般的なインターフェイスから継承する必要はありません。

アイテム #4 Mix-inでミックスする

Rubyにはインタフェースがありませんが、モジュールを使うことでMix-inを実現できます。

モジュールとは...

  • ネームスペースです。(クラスのように)
  • メソッドが定義できます。(クラスのように)
  • インスタンス化はできません。(クラスとは異なり)
  • クラスの中へミックス (組み込み) できます。

冗長な比較演算子

全てのロジックは未満(<)メソッドにあるにも関わらず、その他全ての比較演算子も定義しなければいけません。

  class Pair
    attr_accessor :first, :second
    # ...

    def <(other)
      (first < other.first) ||
        (first == other.first && second < other.second)
    end
    
    def >(other)
      other < self
    end
    
    # 未満(<)に関連する他の比較演算子の定義:
    #     <=, >=, ==
  end

Mix-in の再利用

Mix-inにより、共通点を抜き出すことができます。

  module LessComparable
    def >(other)
      other < self
    end
    # 未満(<)に関連する他の比較演算子の定義:
    #     <=, >=, ==
  end
  
  class Pair
    include LessComparable
    attr_accessor :first, :second
    # ...
    def <(other)
      (first < other.first) ||
        (first == other.first && second < other.second)
    end
  end

アイテム #3 クロージャーの受け入れ

繰り返し

  [1,2,3].each do |item| puts item end

リソース管理

  file_contents = open(file_name) { |f| f.read }

コールバック

  widget.on_button_press { puts "Got Button Press" }

アイテム #2 ri は相方、irb は別の相方

ri
Ruby Information. Ruby標準オブジェクトマニュアル
irb
Interactive Ruby. 対話コンソール型のRubyインタープリタ
$ ri Array
---------------------------------------------------------- Module: Array
     Arrays are ordered, integer-indexed collections of any object.
     Array indexing starts at 0, as in C or Java. A negative index is
     assumed to be relative to the end of the array---that is, an index
     of -1 indicates the last element of the array, -2 is the next to
     last element in the array, and so on.
------------------------------------------------------------------------
Includes:
---------
     Enumerable(all?, any?, collect, detect, each_with_index, entries,
     find, find_all, grep, include?, inject, map, max, member?, min,
     partition, reject, select, sort, sort_by, to_a, zip)
Class methods:
--------------
     [], new
Instance methods:
-----------------
     &, *, +, -, <<, <=>, ==, [], []=, assoc, at, clear, collect,
     collect!, compact, compact!, concat, delete, delete_at, delete_if,
     each, each_index, empty?, eql?, fetch, fill, first, flatten,
     flatten!, frozen?, hash, include?, index, indexes, indices, insert,
     inspect, join, last, length, map, map!, nitems, pack, pop, push,
     rassoc, reject, reject!, replace, reverse, reverse!, reverse_each,
     rindex, select, shift, slice, slice!, sort, sort!, to_a, to_ary,
     to_s, transpose, uniq, uniq!, unshift, values_at, zip, |

RIの別のサンプル

インスタンスメソッドlastに関する問い合わせ結果...

$ ri Array#last
------------------------------------------------------------- Array#last
     array.last     =>  obj or nil
     array.last(n)  =>  an_array
------------------------------------------------------------------------
     Returns the last element(s) of _self_. If the array is empty, the
     first form returns +nil+.

        [ "w", "x", "y", "z" ].last   #=> "z"

IRBサンプル

1+2を行い、Procで定義されているメソッドを取得しています...

$ irb --simple-prompt
>> 1 + 2
=> 3
>> Proc.instance_methods(false)
=> ["[]", "==", "dup", "call", "binding", "to_s",
    "clone", "to_proc", "arity"]

アイテム #1 大量のコードを書かない!

同僚曰く (意訳)

問題を解決するために、Rubyを試しに使ってみました。「コードをちょっと書いてみて...、気が付いたら、それで仕事が終わっていたんです。」


アイテム #0 Rubyプログラムの楽しみを取り戻す

f:id:brazil:20050816050948j:image

知っておくべきその他のこと

まとめ

(10) Rubyの慣習を学ぶ

(9) 全てがオブジェクト

(8) (ほとんど) 全てがメッセージ

(7) Ruby は想像以上にダイナミック

(6) オブジェクトは静的ではない強い型付けを持つ

(5) インターフェイスに関する心配は無用

(4) Mix-inでミックスする

(3) クロージャーの受け入れ

(2) ri は相方、irb は別の相方

(1) 大量のコードを書かない!

(0) Rubyプログラムの楽しみを取り戻す


その他のリンク

Ruby Home Page
http://ruby-lang.org
Francis Hwang’s Blog
http://fhwang.net/blog/40.html
Bob Martin’s Blog
http://www.artima.com/weblogs/viewpost.jsp?thread=4639

ライセンス

この作品は、クリエイティブ・コモンズライセンス(帰属 - 非営利 2.0)の下でライセンスされています。

あなたは以下の条件に従う場合に限り、自由に本作品を複製、頒布、展示、上演、演奏、口述、上映、公衆送信することができます。また二次的著作物を作成することができます。

  • 帰属:あなたは原著作者・実演家のクレジットを出さなければなりません。
  • 非営利:あなたはこの作品を営利目的で利用してはなりません。

(http://creativecommons.org/licenses/by-nc/2.0/ 詳細はこちらを参照)

*1:String Interpolation

*2:Flexible quoting

FawnFawn 2006/07/26 19:20 Good design!
[url=http://swiosxbi.com/vluy/zwcr.html]My homepage[/url] | [url=http://cwdequje.com/lcvu/kxkv.html]Cool site[/url]

JeanJean 2006/07/26 19:20 Good design!
http://swiosxbi.com/vluy/zwcr.html | http://xyuftugt.com/lseb/akwd.html

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証