堀之内 武 2007/08/23
地球惑星流体のデータ処理にRubyをつかうための入門。
Dennou-Rubyチュートリアルページ(基礎編) 水田亮さん作
これ一つで、"Hello, World" からはじめて、配列を使った数学統計演算、数 値計算結果や NetCDF ファイル中のデータの可視化まで体験できるすぐれもの です。
# これをしっかりやってくれれば、あとは余談でいいな。
プログラムの「再利用性」を高めるための仕組み。ここで、再利用とは、書い たプログラムを使い回すこと。「やることは基本的に同じなんだけど、ちょっ と違う」プログラムをたくさん書かざるを得ないとなると、メンテがしんどい。 時間も余計にかかる。一方、もしも再利用性が高いプログラムが書きやす いのなら、Aさんが書いたプログラムを、Bさんが似て非なることをするの に「そのまま」利用できる可能性を高くできるはずである。よって、コミュ ニティの共有ソフトウェア資産を作るのに適していると言える。
再利用性を高めるためにオブジェクト指向で導入された重要な仕組みは
である。また、前者を無駄なく実現する手段として
がある。
オブジェクト (プログラム中の処理の単位. 「変数で表せるもの」と思ってもいい) に対する操作をオブジェクトの側に定義することで、 内容が基本的に同じ (でも実際にやることの詳細は違うかもな) 命令に対し、同じ名前をつけられ ることをいう。
オブジェクト指向ではない C ならば、例えば次のように、それぞれ別個の名 前をつけなければならない。
対象が NetCDF ファイルなら、変数 file をファイルポインターとして
nc_read( file, buf );
対象が Grib ファイルなら、変数 file をファイルポインターとして
grib_read( file, buf );
よって、ファイルを読込んで何か操作をするプログラムを書こうと思うと、対 象のファイルの種類にあわせて、この部分を変えないとならない。これは似て 非なるコードの氾濫のもとである。
一方、(架空の)オブジェクト指向言語なら
対象が NetCDF ファイルでも Grib ファイルでも、変数 file をファイルポインターとして
file.read( buf );
よって、ファイルの種類に関わらず、読み出しプログラムは共通にできる。
コラム 「型宣言」 そうは言っても、C や Fortran のようにように、変数の型(オブジェクト 指向ならクラス)を陽に定義しなればならなかったとしたら、実は、ここ至 る前に変数 file を宣言のするところで引っ掛かって共通にできない可能性 がある。静的な型付けを持つ言語では、それを避けるための工夫が必要であ る。一方、Rubyは静的な型付けがなく型宣言文が不要であるため、その問題 は最初からない。宣言文が要らないということは、その分ソースが短く済み、 速く書けるということにもつながる。
-
コラム 「Fortran90 とポリモーフィズム」 Fortran90では「総称名定義」が導入された。これはポリモーフィズムを実 現するためである。このように伝統的な言語もオブジェクト指向の手法を取 り入れることで、再利用性を高める努力がなされている。ただ、やってみる と Fortran90 で総称名を定義するのは、結構手間だったりする。ときに手 間が増えすぎて、どうも本末転倒に感じることも...。Rubyを使い出したら、 やはりFortranでオブジェクト指向するって大変だったんだとな実感しまし た。(でも、もちろん Fortran のほうが実行は高速。)
ポリモーフィズムで共通化できるのは、呼び出し側のコードである。上の例で 言えば、呼び出される側の関数(以後「メソッド」と呼ぶ)である read は NetCDF 用と grib 用でそれぞれ定義しなければならない。実際、ファイルの 構造が違うため読み方が違うなら、それはどうしようもないことである。
しかし、異なるファイル形式に対しても同一の操作で出来ることもあるであろ う。例えばファイル長を求めるだけなら、中身の形式は関係ないはず。両者が 共通の「親」を持つようにし、共通な操作は親で定義できれば、無駄が省ける。
このように親となるデータの型(クラスと呼ぶ)から、子となるデータ型 (NetCDFファイルクラスとGribファイルクラス)が、メソッドを引き継ぐこと を「継承」と呼ぶ。子クラスにおいては、親クラスとは違う実装をしなければ ならないメソッドのみを再定義すれば良い。
継承は、静的な片付けを持つ言語では、型宣言をしつつ汎用性を保つ手段とし ても利用される。
オブジェクトの内部データを隠す(閉じ込める = encapsulate)ことで、それ を利用するプログラムがいつのまにか特定クラスのオブジェクトの内部構造に 依存しないようにすること。実は、すべての「オブジェクト指向」言語で徹底 されているわけではなく、そのため問題が生じる場合がある。Rubyは大丈夫。
きちんとカプセル化できる言語では、オブジェクトはブラックボックスになる ため、公開されたインタフェースを通じてしかアクセスできなくなる。すると、 例えば、「ファイルを表すクラスがサポートすべき機能はこれこれである」と いうことでインタフェースを設計し、それしか持たせないようにすれば、利用 者が不用意に特定のファイル形式だけが持つ何かを利用できなくなる。つまり、 「知らず知らずのうちに汎用性を損ねるプログラムを書いてしまう」ことが避 けられるのである。(ここではカプセル化できる言語なら無条件にそうなると は言ってないことに注意。)
「アヒルのように歩きアヒルのように鳴くやつはアヒルだ。」 (Wikipediaの解説 参照。)
カプセル化がしっかりしてれば、プログラムの適用可能性を決めるのは対象の 中身でなく振る舞いである。
本来は、静的型付けなんてなくたって大丈夫だというための例え。
irb に打ち込んでみよう。
$ irb --simple-prompt >> Object.new.methods.sort => ["==", "===", "=~", "__id__", "__send__", "class", "clone", "display", "dup", "eql?", "equal?", "extend", "freeze", "frozen?", "hash", "id", "inspect", "instance_eval", "instance_of?", "instance_variable_get", "instance_variable_set", "instance_variables", "is_a?", "kind_of?", "method", "methods", "nil?", "object_id", "private_methods", "protected_methods", "public_methods", "respond_to?", "send", "singleton_methods", "taint", "tainted?", "to_a", "to_s", "type", "untaint"]
これだけのメソッドはどのオブジェクトも持っているということ. では...
>> (1.0).class => Float >> str = "mojiretsu" => "mojiretsu" >> str.class => String >> str2 = str.clone => "mojiretsu" >> str == str2 => true >> p str2.object_id, str.object_id 537374530 537388300 => nil
>> array = [1, 3, "STR"] => [1, 3, "STR"] >> array.class => Array >> (array.methods - Object.new.methods).sort => ["&", "*", "+", "-", "<<", "<=>", "[]", "[]=", "all?", "any?", "assoc", "at", "clear", "collect", "collect!", "compact", "compact!", "concat", "delete", "delete_at", "delete_if", "detect", "each", "each_index", "each_with_index", "empty?", "entries", "fetch", "fill", "find", "find_all", "first", "flatten", "flatten!", "grep", "include?", "index", "indexes", "indices", "inject", "insert", "join", "last", "length", "map", "map!", "max", "member?", "min", "nitems", "pack", "partition", "pop", "push", "rassoc", "reject", "reject!", "replace", "reverse", "reverse!", "reverse_each", "rindex", "select", "shift", "size", "slice", "slice!", "sort", "sort!", "sort_by", "to_ary", "transpose", "uniq", "uniq!", "unshift", "values_at", "zip", "|"]
最後の、(array.methods - Object.new.methods).sort
は、Array の持つメソッド名から、Object なら何でも持っている一般的な
ものを取り除き、sort して表示する。このパターンを覚えれば、マニュア
ルを見なくてもある程度何ができるかが調べられる。今、配列の長さを知りた
いとしよう。「長さは英語なら length だから、とりあえず len という文字
列を含むメソッドを探してみよう」と思ったらこう:
>> array.methods.grep(/len/) => ["length"] >> array.length => 3
Ruby の配列(Array)は1次元限定。モノを順番に並べる箱といったとこ。伸縮 自在で、中身はなんでも良い。
例
...
配列の要素は先頭からの順序で取出すが、順序でなく名前(キー)と値を対応づ ける連想配列もある。便利なので、慣れるとこれなしではプログラミングした くなくなる。キーは文字列とは限らず何でもいい。内部的にはキーをそのまま 使うのでなく、Hash関数を適用して得られる整数と対応づけるようになってい る実装により、Hashクラスという名前になっている。
例
...