メソッドの引数にデフォルト値を定めて省略可能にしたいことは、良くあるでしょう。 これは以下のように、メソッドの引数定義を「変数名=デフォルト値」と書く ことで実現できます。デフォルト値を与えた引数は呼出し時に省略できます。
def set_window( xmin=0.2, xmax=0.8, ymin=0.2, ymax=0.8 ) hogehoge( xmin, xmax, ymin, ymax ) ... end set_window # デフォルト値を使う set_window(0.1,0.9,0.1,0.9) # 陽に指定する |
オプション引数を多くサポートしたい場合、順番でなく名前(キーワード) で指定したいことでしょう。Rubyにはキーワード引数はありませんが、 Hash を使うことで同等のことが出来ます。 これに限らず Hash は非常に使いでがありますので、是非なれ親しんで下さい。
require "numru/dcl" include NumRu def plot(x, y=nil, options=nil) if y == nil y = x x = NArray.float(y.length).indgen! end DCL.grfrm if options if (val=options[:xtitle]); DCL.uscset("cxttl", val); end if (val=options[:ytitle]); DCL.uscset("cyttl", val); end end DCL.usgrph(x, y) end x = NArray.float(20).indgen! - 10 z = x ** 2 / 20 DCL.gropn(1) plot(z) # キーワード引数なし plot(z, nil, {:xtitle=>"X", :ytitle=>"Y"}) # Hashによる「キーワード引数」 plot(x, z, :xtitle=>"X", :ytitle=>"Y") # 呼出し末尾では {} は省略可 DCL.grcls |
これを実行すると、以下のような図が表示されるはずです+
上の例で、{}を省略した最後の表現
plot(x, z, :xtitle=>"X", :ytitle=>"Y")
はだいぶキーワード引数の雰囲気を漂わせていますね。なお、
ここではタイプの量を減らすため、キーワードとして、コンマで始まる
Symbol (:xtitleなど) を使いましたが、文字列もよく使われます。
Symbol の値は実行時に内部的に割り振られますから、
例えば一度描いた図をのちに再現するためにキーワードもファイルに
出力して保存する場合は文字列を使うべきです。
ちょと混乱しやすいことについて。 Rubyでは、「全てがオブジェクト」ですが、オブジェクトは Ruby セッション中にメモリー上に存在する何かであって、 プログラム中の変数そのものではありません。 後者はあくまで変数名です。だから、以下のようになります。 (irb(main):001:0>等は irb のプロンプト)
% irb irb(main):001:0> a = [10, 20] => [10, 20] irb(main):002:0> b = a => [10, 20] irb(main):003:0> a.id => 427272 irb(main):004:0> b.id => 427272 irb(main):005:0> b[0] = 999 => 999 irb(main):006:0> a => [999, 20] |
上では a と b が同じ id を持つことから、 同じオブジェクトであることがわかります。従って、 b に代入すると、a の値も変っています。 a や b はオブジェクトそのものではなく、ラベルでしかありません。 変数(名)とオブジェクトの関係は、 C言語におけるポインターとその参照先のようなものです。 a と b を別オブジェクトにしたい場合は、clone メソッドを使います:
% irb irb(main):001:0> a = [10, 20] => [10, 20] irb(main):002:0> b = a.clone => [10, 20] irb(main):003:0> a.id => 425568 irb(main):004:0> b.id => 415692 irb(main):005:0> b[0] = 999 => 999 irb(main):006:0> a => [10, 20] |
cloneメソッドは、オブジェクトのコピーを作ります。 ただし、参照先を追い掛けていって、その全てのコピーを作る "deep" なクローンではなく、そのオブジェクトだけをコピーする "shallow" なクローンです
さて、Cのポインターのようなものと言えば、迷子が発生する 恐れがあるでしょう。例えば、
a = [ 10, 20 ] a = 1 p a => 1 |
の2行目のように、同じ変数名 a を別のオブジェクトを指すようにすると、 その前に指していた配列 [10,20] はどこからも参照できなくなります。 この迷子を、一般にゴミ(Garbage)と呼びます。C のプログラミングの教室では、 ゴミを作らないように自分で管理するよう言われるでしょうが、 Ruby は現代的な言語の例に漏れず、裏でゴミを集めていて回収する、 「ゴミ集め(Garbage Collection)」の機能があります。 ちなみに Fortran90 を使っている方、 ポインターを使えばやはりゴミが溜っていく危険がありますが、 もちろん Fortran90 にゴミ集めはありません。ご注意を。 オブジェクト指向プログラミングではゴミは溜りやすいので、 ゴミ集め機能のないオブジェクト指向言語を使うのは極力避けましょう(^_^)。
「オブジェクト指向とは何か?」はさておき、 「オブジェクト指向とは何のための技術であるか?」と問えば、 (少なくとも筆者の) 答えは簡単で、 より信頼性・発展性があるプログラムをより少ない労力で作るための技術 であるということになる。 その観点から、「ちょっと進んだ使いかた編」で扱った NArrayMiss クラス を題材に、オブエジェクト指向で重要な要素を簡単に解説する。
以上述べてきた機能や考えは、次項の GPhys ライブラリーにおいて活用されている。
GPhysとは?
何につかうのか?
開発の現状
まだ最初期段階である(1年前に卒業した修士の学生(川那辺君)と共 同で開発した。それは廃棄してごく最近完全に最初から作り直している)。 ソースコードは(今のところ)約2000行とコンパクト。 GPhysをダウンロードして展開し、インストールしよう。
% tar xvzf gphys-0.0.3.tar.gz % cd gphys-0.0.3 % ruby install.rb |
まずは絵を描いてみよう。 gphys-0.0.3 の配布パッケージにはテスト用に、T.jan.ncという NetCDFファイルが含まれているので、それを使う。 中身は NCEP 再解析による、全球の気温の気候値である。 まずは以下を実行してみよう。
% cd gphys-0.0.3/lib % ruby ggraph_old.rb |
ここで ggraph_old.rb は、GPhys の可視化ライブラリーのソースコード であるが、それ自身を ruby コマンドにソースファイルとして渡すと 末尾のテストプログラムが実行されるようになっている。 なお、ggraph_old.rb という名前になっているのは、 グラフィックライブラリー(ggraph.rb)を全面的に作り直す予定で、 作業中だからという事情による。
本プログラムを実行すると、下のような気温データの図が2枚表示される
であろう。
テストプログラム本体を少し簡潔にしたものは以下のようになる。
if $0 == __FILE__ include NumRu file = NetCDF.open("../testdata/T.jan.nc") temp = GPhys::NetCDF_IO.open(file,"T") DCL.uzfact(0.6) GGraph.open(1) GGraph.contour(temp.mean(0)) GGraph.contour(temp[true,true,5]) GGraph.close end |
全体を囲む if ブロックにより、
$0 (rubyコマンドに与えたソースファイル) がこのファイルの名前
__FILE__ (ファイル名を収める組み込み変数) に等しい場合に、
その中身が実行されることがわかる。
これをライブラリーから切り離して別ファイルにして実行するには以下のように
require を最初に呼ぶ。($0 == __FILE__)の制限はもはやいらない。
require "numru/ggraph_old" include NumRu file = NetCDF.open("../testdata/T.jan.nc") temp = GPhys::NetCDF_IO.open(file,"T") DCL.uzfact(0.6) GGraph.open(1) GGraph.contour(temp.mean(0)) GGraph.contour(temp[true,true,5]) GGraph.close |
上のソースを irb で一行ずつ打ち込んでみよう。なお、
GGraph.open(1)
を
GGraph.open(2)
に変えると、postscriptファイルが出来るので、印刷できる。
ここではまず、ファイル "T.jan.nc" 中の変数 "T" を開き、 GPhys オブジェクトを構成して(GPhys::NetCDF_IO.openによる)、 temp という変数名をつけている。この東西平均( temp.mean(0) ) 並びに下から5番目のレベルでの断面(temp[true,true,5])を とって、図示している。temp はGPhys のオブジェクトであるが、 平均やサブセットの取り方が NArray のそれと全く同じであることに注意せよ。
可視化を行うのはモジュール GGraph のメソッド contour である。 その引数は uwnd (の平均やサブセット)のみであるが、表示される 座標軸やタイトルが正しく書けていることに注意せよ。 モジュール GPhys::NetCDF_IO が NetCDF の規則を知っており、 GPhys::NetCDF_IO.open はその規則に従った解釈を行って座標情報を 読み取り、GPhys オブジェクトを構成しているのである。 GGraph は GPhys の一部ではなく独立であるため、GPhysオブジェクトの 内部情報に直接アクセスすることは出来ない。GGraph.contour は、 あくまで GPhys が外部に公開しているインターフェースを通じて、 問合わせを行い、その結果に基づいて可視化しているのである。 このため、GPhys クラスに何らかの変更があっても、 GPhys の公開インターフェースの外部への仕様が変らない限り、 影響を被らない。---(情報隠蔽)
上に、GPhysオブジェクトたる temp は NArray と全く同じ
やりかたで平均が取れる(temp.mean(0))と書いた。これは
内部で然るべきデータに関し NArray と同様な単純平均が
とられているのではない。実は平均の取り方そのものが再定義
されており、離散化された座標軸にそっての積分を積分区間の
長さで割ったものとなっている。
---(多態性)
ファイル中のデータは本当に必要になるまで、
読み込みが行われないようになっている(上の例では、
平均をとる時点で、また平均を取らないほうの例では可視化
されるときに初めて、値が読みこまれる)
---(実行遅延)
実は、上の例で用いた GGraph による描画では、描画結果を
「コマンドオブジェクト」(またはその集合)として返すようになっている。
ソースを以下のように変更すると、描いたものを再描画する。
require "numru/ggraph" include NumRu file = NetCDF.open("../testdata/T.jan.nc") temp = GPhys::NetCDF_IO.open(file,"T") DCL.uzfact(0.6) gobjs = [ GGraph.open(1) , # 実は各描画命令はオブジェクト化されて各メソッドの GGraph.contour(temp.mean(0)) , # 戻り値となっている。複合なら配列として GGraph.contour(temp[true,true,5]) , GGraph.close ] gobjs.flatten! # 入れ子になってる配列をフラットに gobjs.each{|i| p i.class} # 各コマンドのクラスを表示してみる gobjs.each{|i| i.exec} # 再実行 --- 先ほど描いたものが再現される |
GPhys のソースディレクトリーに存在する各ファイルには、 それぞれ末尾にテストプログラムが付いているので、 実行してみると良い。
% ruby attribute.rb % ruby attributenetcdf.rb % ruby axis.rb % ruby ggraph.rb % ruby gphys.rb % ruby gphys_netcdf_io.rb % ruby grid.rb % ruby subsetmapping.rb % ruby varray.rb % ruby varraynetcdf.rb |
GPhys の構成とその他の機能については、 こちらの発表資料に概要が説明されている ここでは、以下のように中身を調べてみよう。irbへの入力と出力を そのまま表示する。入力は、irb(main):001:0> 等のプロンプトに 続く部分である。# に続く部分は後から付け加えたコメントである。
% irb irb(main):001:0> require "numru/ggraph_old" => true irb(main):002:0> include NumRu => Object irb(main):003:0> file = NetCDF.open("../testdata/T.jan.nc") => NetCDF:../testdata/T.jan.nc irb(main):004:0> temp = GPhys::NetCDF_IO.open(file,"T") => |
Copyright (C) 2003 GFD Dennou Club. All Rights Reserved.