ここまで、私たちは、数々のオブジェクトを異なる種類、あるいは別の言い方を すればクラス ごとに分けて見てきました。 たとえば、文字列、整数、浮動小数点数、配列、そして後で説明する 特別なオブジェクト(true, false, そして nil)などです。 Rubyでは、これらのクラスはいつも先頭文字を大文字で書かれます。つまり、 String(文字列), Integer(整数), Float(浮動小数点数), Array(配列)... などなどです。 一般的にはある特定のクラスの新しいオブジェクトを作りたいときには、new を使います。
a = Array.new + [12345] # Array の足し算。 b = String.new + 'hello' # String の足し算。 c = Time.new puts 'a = '+a.to_s puts 'b = '+b.to_s puts 'c = '+c.to_s
a = 12345 b = hello c = Mon May 11 18:44:26 +0900 2009
配列や文字列は、それぞれ[...]とか'...'のように 作ることができるので、newを使って作ることはほとんどありません。 (上の例からはあまりはっきりしないかもしれませんが、String.newは 空の文字列を、Array.newは空の配列を作ります。) 数字もまた例外です。整数はInteger.newでは作ることができません。 整数が欲しければそのまま整数を書かなければなりません。
それでは、Timeクラスに関してはどうでしょう? Timeオブジェクトはある瞬間の時刻を表します。 ある時刻に数を足したり引いたりして新しい時刻を作ることが出来ます。 ある時刻に1.5を加えると、1.5秒後の新しい時刻が作られます。
time = Time.new # このWebページをゲットした瞬間。 time2 = time + 60 # 1分後。 puts time puts time2
Mon May 11 18:44:26 +0900 2009 Mon May 11 18:45:26 +0900 2009
また、ある特定の瞬間の時刻をTime.mktimeを使って 作ることも出来ます。
puts Time.mktime(2000, 1, 1) # Y2K. puts Time.mktime(1976, 8, 3, 10, 11) # 私の生まれたとき。
Sat Jan 01 00:00:00 +0900 2000 Tue Aug 03 10:11:00 +0900 1976
注意:この誕生時刻は、太平洋夏時間(Pacific Daylight Time; PDT)で 表しています。Y2Kが問題になったのは、少なくとも西海岸の人にとっては、 太平洋標準時(Pacific Standard Time; PST)です。 (訳註;日本国内では時刻は日本標準時(JST)で表され、グリニッジ標準時(GMT) あるいは協定世界時(UT)より9時間進んでいます。そのため、GMT+9:00と表現されることも あります。PDTはGMTより7時間、PSTは8時間遅れています。 mktimeはコンピュータに設定されたタイムゾーンにあわせて時刻を設定するので、 実行するコンピュータによりこの結果は異なります。) カッコはmktimeの引数を一つに束ねる働きをしています。 引数を加えていくほど、時刻は正確になります。
時刻を比較メソッドを使って比較することも出来ます。 (早い時刻は遅い時刻より小さく なります。) そして、ある時刻を別の時刻から引き算をするとその2つの時刻の間の 秒数を得ることが出来ます。これらを使っていろいろ遊んでみましょう。
• 10億(One billion)秒... (もし記録が残っているなら)あなたの生まれた 正確な時刻を見つけ、いつ10億秒歳になる(あるいはなった)のかを 計算してみなさい。そしてカレンダーに印をつけましょう。
• ハッピーバースデー! 生まれた年、月、そして日を順に訊いて そこから年齢を計算します。そして、過ごしてきた誕生日それぞれに対して 大きなおめでとう!をプレゼントしましょう。
もう1つ、便利なクラスHashがあります。 ハッシュは配列に非常に似ていて、いろいろな種類のオブジェクトを 指し示すことの出来る項目のようなものをまとめています。ただし、 配列ではそれらの項目は1列に並んでおり、それらの1つ1つには (0から)数字が振られているのに対して、ハッシュではその項目が 並んではおらず(ある意味ごちゃ混ぜになっていて)、それぞれの 項目を参照するのに、数ではなくどんな オブジェクトも 使うことが出来ます。まとめて処理したいひとまとまりのデータがあって、 それが序列をつけたリストとしてはそぐわない時などが、ハッシュ の出番です。例えば、このチュートリアルを作るときのコードの異なる部分 に対して使う色などです。
colorArray = [] # Array.new と同じ colorHash = {} # Hash.new と同じ colorArray[0] = '赤' colorArray[1] = '緑' colorArray[2] = '青' colorHash['strings'] = '赤' colorHash['numbers'] = '緑' colorHash['keywords'] = '青' colorArray.each do |color| puts color end colorHash.each do |codeType, color| puts codeType + ': ' + color end
赤 緑 青 strings: 赤 keywords: 青 numbers: 緑
配列を使った場合は、0の項目が文字列を表し、 1が数字を...というように覚えていなければなりません。 でも、もしハッシュを使えば簡単になります。項目の名前を'string'にして、 もちろんここに文字列の色を入れるのです。面倒な対応などは覚える必要がありません。 ここで注意しておいたほうが良いのは、eachを使った時、 ハッシュに入っているオブジェクトがハッシュに入れた時と同じ順序で 取り出されるとは限らないことです。 (少なくとも、私がこれを書いたときは違いました。次は順番どおり出てくるかもしれません。 どちらにしても、ハッシュの順序に関してはわからないのです。) 配列はものの順序を保持しますが、ハッシュはしません。
ハッシュの項目の名前としては文字列が使われることが普通ですが、やろうと思えば どんな種類のオブジェクトでも使うことができます。(それが必要になるとは あまり思えませんが)、配列や他のハッシュだってOKです。
weirdHash = Hash.new weirdHash[12] = 'モンキーズ' weirdHash[[]] = 'からっぽ' weirdHash[Time.new] = 'するなら今だ'
ハッシュと配列はそれぞれ適材適所があります。ある特定の問題に 対し、どちらが最適かを決めるのはあなたの責任です。
前の章の最後で、与えられた整数を英語のスペリングに変換するメソッドを 書きました。しかし、それは整数が持つメソッドではなく、単に 一般的な「プログラム」が持っているメソッドでした。たとえば englishNumber 22と書く代わりに、22.to_engのように 書くことができたらナイスだと思いませんか? こうすればそれができます。
class Integer def to_eng if self == 5 english = 'five' else english = 'fifty-eight' end english end end # 2つの数字についてチェックしましょう。 puts 5.to_eng puts 58.to_eng
five fifty-eight
はい、テスト完了。動いているようです。 :)
さて、これで整数メソッドを定義したことになります。 Integerクラスに飛び込み、そこでメソッドを定義し、 そしてそこから飛び出してきました。 その結果、すべての整数はこの(ある意味不完全な)メソッドを持つように なります。実際、もしto_sのようなビルトインメソッド (最初から定義されているメソッド)の振る舞いが気に入らないとしたら、 このやり方を使って再定義ができてしまいます...しかしお勧めはしませんが。 何か新しいことをしたくなったときは、古いメソッドをそのままにして 新しい物を作るほうが良いようです。
さて、まだ混乱していますか? このプログラムをもう少し見ていきましょう。 ここまで、どんなコードを実行するにしてもメソッドを作るにしても、それは デフォルトの「プログラム」オブジェクトの中で行ってきました。 でも、今のプログラムでは、最初にそのオブジェクトから離れて、 Integerというクラスの中に入っています。 そして、そこでメソッドを定義し(これによって整数メソッドができます。)、 すべての整数がそれを使うことができるようになりました。 メソッドの内側では、そのメソッドを使っているオブジェクト(つまり整数)を 参照するのにselfを使います。
今までにたくさんのオブジェクトの種類(クラス)を見てきました。 しかし、Rubyが持っていないオブジェクトの種類が欲しくなることは良く あります。 ラッキーなことに、新しいクラスを作るのは古いクラスを拡張するのと 同じくらい簡単です。たとえば、Rubyの中にさいころが 欲しくなったとしましょう。これがDieクラスの作り方です。
class Die def roll 1 + rand(6) end end # 2つのさいころを作ってみましょう。(訳註:die(さいころ)の複数がdice) dice = [Die.new, Die.new] # そして、転がします。 dice.each do |die| puts die.roll end
4 2
(もし、乱数の節を飛ばしていたとしたら、rand(6)というのは、 0から5までの乱数を与えるというふうに理解してください。)
はい、私たちのオブジェクトが出来ました。 さいころを(リロードボタンを使って)何度か転がしてみて、何が出るか みてみましょう。
これで、オブジェクトに対してどんな種類のメソッドも定義できるようになりました...。 しかし何かまだ足りないようです。 こんなふうにオブジェクトを作ったりしていると、何か変数を覚える前にしていた プログラミングのような感覚になりませんか? たとえば、今作ったさいころのプログラムを見てみましょう。 さいころを振ることはできます。そして、毎回違った数字が現れています。 でも、その数字をとどめておきたいとしたら、その数字を指し示す変数が欲しくなります。 まともなさいころなら、数をひとつ保持 することができて、 さいころを振ったときに、その数が変化するようにすべきだと思います。 もし、さいころ自体を変数に入れているなら、出ている目を別の変数にいれる というのは良くないでしょう。
でも、もし出た目をrollの中で(局所)変数に保存しようとすると、 rollが終わるやいなや、その値はどこかに行ってしまいます。 この数は別の種類の変数に保存する必要があるようです。
普通、ある文字列について話をしたい時には、それを文字列 と呼びます。 でも、文字列オブジェクト と呼ぶこともできます。 時々、プログラマはこれをString(文字列)クラスの インスタンス と呼んだりします。でもこれは、文字列 の やや気取った(そして、ちょっと長たらしい)言い方に過ぎません。 要するに、クラスのインスタンス とは、クラスのオブジェクトのことです。
さてそれで、インスタンス変数とはオブジェクトの変数のことです。 メソッドの局所変数はメソッドが終わるまでの命です。 一方、オブジェクトのインスタンス変数は、オブジェクトが生き残っている限り 生き残ります。インスタンス変数を普通の局所変数と区別するために、 その名前の先頭には@をつけます。
class Die def roll @numberShowing = 1 + rand(6) end def showing @numberShowing end end die = Die.new die.roll puts die.showing puts die.showing die.roll puts die.showing puts die.showing
1 1 5 5
いいですねぇ。これで、rollはさいころを振ってshowingは 何の目が出ているかを知らせるようになりました。 でも、もし、さいころを振る前(つまり、@numberShowingの変数を セットする前)に何の目が出るかを見ようとしたら、いったいどうなるでしょう。
class Die def roll @numberShowing = 1 + rand(6) end def showing @numberShowing end end # このさいころをもう一度使うつもりはないので、 # 変数に保存しておく必要はありません。 puts Die.new.showing
nil
ふむふむ。少なくともエラーにはならないようです。でもこの、 nilという出力の意味を推し量ったとしても、 「振られていない」さいころに出た目というのにはやはり違和感があります。 これを避けるには、さいころオブジェクトが作られた瞬間に出る目を 決めてしまえば良いわけです。これをするのがinitializeです。
class Die def initialize # ここでは、さいころを振るだけにします。 # 最初から6にセットしておくとか、 # 他にやりたい放題できることは確かですけど。 roll end def roll @numberShowing = 1 + rand(6) end def showing @numberShowing end end puts Die.new.showing
4
オブジェクトが生成されるときには、(それが定義されてれば) 必ずinitializeメソッドが実行されます。
私たちのさいころはほとんど完璧です。あと、出る目をセットする方法が あったらいいなと思いませんか? さぁ、このcheatメソッドを 書いてみましょう。プログラムを書いたら(そして、もちろん書いた メソッドがきちんと動くかテストしたら)この文章に戻ってきましょう。 7の目をセットできてしまうなんてことがないように 確認してくださいね。
ちょっとトリッキーでしたが、これでかなりクールなことができるように なりましたね。 さて、それでは、また別のもっと楽しい例をあげてみましょう。 たとえば、簡単な仮想ペットを作ってみるのはどうでしょう。 赤ちゃんドラゴンです。 たいがいの赤ちゃんと同じく、そのドラゴンは、食べ、寝、うんちをします。 ということは、食べさせたり、寝かしつけたり、散歩させたり、もできるように しないといけません。内部では、ドラゴンはおなかがすいているのか、 疲れているのか、出かけたいのかなどを記憶している必要があるのですが、 赤ちゃんドラゴンにそれを聞いてみてもそれはわかりません。 人間の赤ちゃんに「おなかすいた?」と訊いてもしかたがないのと同じです。 その代わり、赤ちゃんドラゴンと会話するための別の方法を2,3提供しましょう。 そして、彼が生まれる時には名前を付けられるようにします。 (newメソッドに何を渡しても、それはinitializeメソッドに引き継がれます。) OK, では、はじめます。
class Dragon def initialize name @name = name @asleep = false @stuffInBelly = 10 # 最初彼はおなかいっぱいです。 @stuffInIntestine = 0 # トイレはまだ大丈夫。(Bellyは胃、Intestineは腸です。) puts @name + ' は生まれました.' end def feed puts 'あなたは ' + @name + 'に食べ物を与えます.' @stuffInBelly = 10 passageOfTime end def walk puts 'あなたは ' + @name + 'をトイレに連れて行きます.' @stuffInIntestine = 0 passageOfTime end def putToBed puts 'あなたは ' + @name + ' を寝かしつけます.' @asleep = true 3.times do if @asleep passageOfTime end if @asleep puts @name + ' はいびきをかいて, 部屋中煙だらけ.' end end if @asleep @asleep = false puts @name + ' はゆっくり目を覚ます.' end end def toss puts 'あなたは ' + @name + ' を空中に投げ上げます.' puts '彼はキャハキャハ笑い, あなたの眉毛は焼け焦げた.' passageOfTime end def rock puts 'あなたは ' + @name + ' を優しく揺すります.' @asleep = true puts '彼は短くうとうと...' passageOfTime if @asleep @asleep = false puts '...でもやめるとすぐ起きちゃう.' end end private # "private"というのは、ここで定義されているメソッドが # オブジェクト内部のものであるという意味です。 # (あなたはドラゴンにえさを与えることはできますが、 # おなかがすいているかどうかを訊くことはできません。) def hungry? # メソッドの名前は"?"で終わることができます。 # 通常、メソッドがtrueかfalseのどちらかを返すときだけ、 # この名前を使います。このように: @stuffInBelly <= 2 end def poopy? #うんちが出そう? @stuffInIntestine >= 8 end def passageOfTime if @stuffInBelly > 0 # 食べたものは、胃から腸へ移動 @stuffInBelly = @stuffInBelly - 1 @stuffInIntestine = @stuffInIntestine + 1 else # 私たちのドラゴンは飢えました! if @asleep @asleep = false puts '彼は突然跳び起きます!' end puts @name + ' は飢え死にしそう! 死に物狂いの彼は"あなた"を食べてしまいました!' exit # プログラムを終了します。 end if @stuffInIntestine >= 10 @stuffInIntestine = 0 puts 'おっと! ' + @name + ' はやっちゃったようです...' end if hungry? if @asleep @asleep = false puts '彼は突然起きます!' end puts @name + 'のおなかはゴロゴロ言ってます...' end if poopy? if @asleep @asleep = false puts '彼は突然起きます!' end puts @name + ' はうんちをしたくて踊っています...' end end end pet = Dragon.new 'Norbert' pet.feed pet.toss pet.walk pet.putToBed pet.rock pet.putToBed pet.putToBed pet.putToBed pet.putToBed
Norbert は生まれました. あなたは Norbertに食べ物を与えます. あなたは Norbert を空中に投げ上げます. 彼はキャハキャハ笑い, あなたの眉毛は焼け焦げた. あなたは Norbertをトイレに連れて行きます. あなたは Norbert を寝かしつけます. Norbert はいびきをかいて, 部屋中煙だらけ. Norbert はいびきをかいて, 部屋中煙だらけ. Norbert はいびきをかいて, 部屋中煙だらけ. Norbert はゆっくり目を覚ます. あなたは Norbert を優しく揺すります. 彼は短くうとうと... ...でもやめるとすぐ起きちゃう. あなたは Norbert を寝かしつけます. 彼は突然起きます! Norbertのおなかはゴロゴロ言ってます... あなたは Norbert を寝かしつけます. 彼は突然起きます! Norbertのおなかはゴロゴロ言ってます... あなたは Norbert を寝かしつけます. 彼は突然起きます! Norbertのおなかはゴロゴロ言ってます... Norbert はうんちをしたくて踊っています... あなたは Norbert を寝かしつけます. 彼は突然跳び起きます! Norbert は飢え死にしそう! 死に物狂いの彼は"あなた"を食べてしまいました!
ふー! もちろん、会話的にプログラムが動けばなお良いのですが、 その部分は後であなたが作ることができるでしょう。 ここでは、新しいドラゴンクラスを作るのに直接関係する部分を示してみました。
この例では、2つの新しいものが出てきています。 1つ目はexitです。 これは非常に単純で、プログラムをその場で終了させるメソッドです。 2つ目はprivateという言葉です。 これはクラス定義の真ん中に挿入されています。 プログラムはこれがなくても動くのですが、 ドラゴンに対してある特定のメソッドだけを行うことができて、 それ以外はドラゴン内部で起こるようにする、 という制限をつけたい時に必要です。 これは、「フードの下に覆い隠す」というふうに思えば分かるのではないかと思います。 別の例を挙げましょう。運転する時、あなたが知っていなければならない操作は、 アクセルペダルとブレーキペダルの踏み方、ハンドルの回し方などです (自動車のメカニックでない限り)。 プログラマはこれを車に対するパブリック インターフェイス と呼びます。 エアバッグがいつ開くかというのは車の内部の問題です。 普通のユーザ(ドライバー)はこのことを知る必要はありません。
これを、もう少し具体的にプログラムの行と関連させるため、 (たまたま私の仕事と関係あるのですが)、ビデオゲームの中の車をどんなふうに 作るかについて考えてみましょう。 最初に、パブリックインターフェイスをどうするかを決めたくなると思います。 別の言い方をすれば、あなたの車オブジェクトに対して、人々がどのメソッドを 呼ぶことが出来るようにするかということです。 おそらく、アクセルペダルとブレーキペダルを踏めるようにするのは 必要だし、どのくらい強くペダルを踏んでいるのかを特定するのも 必要でしょう。(べた踏みと軽くたたくのでは大きな違いです。) それから、ハンドルを切るのも必要でしょうし、どのくらい回すのかを 言う必要だってあるでしょう。それと、クラッチをつけたくなったり、 ウインカーをつけたくなったり、ロケットランチャー、アフターバーナー、 キャパシタ、などなど・・・。要はどんなゲームを作っているかによります。
で、車オブジェクトの内部ではずっとたくさんのことが行われている必要が ありますが、それだけではなくて、(最も基本的なものとして) 車のスピード、方向、位置などが必要です。 こういった属性はアクセルペダルやブレーキペダルを踏むことと、 ハンドルを回すことで変化するでしょうが、もちろん、ユーザーが直接 位置を指定できたりしてはまずいです(まるで、ワープのようになって しまいますから)。あるいは、スリップ、ダメージ、空気抵抗など を記録したくなるかもしれません。これら全ては車オブジェクトの内部 のものです。
• OrangeTreeクラスを作ってみなさい。 このクラスには、木の高さを返すheightメソッドと、 メソッドを呼ぶことで、1年分時間を進めるoneYearPassesメソッドがあります。 毎年、この木は成長し大きくなります(オレンジの木が1年に伸びる分だけ)。 そして、ある年限が来たら(これもメソッド呼び出しによります) その木は死んでしまいます。最初の2,3年は実をつけませんが、その後は 実がなる様にします。で、成長した木は若い木よりたくさん実をつけます。 このあたりはあなたが納得するよう調節してみましょう。 そして、もちろんcountTheOranges(木になっているオレンジの数を返す)メソッドと、 pichAnOrange(オレンジをひとつ摘むメソッド。このメソッドで @orangeCountが、1だけ小さくなり、いかにおいしいオレンジだったかを告げる文字列か あるいは、もう木にオレンジがなっていないことを告げる文字列かを返します。)を 実行できるようにしなければいけません。 それと、ある年に取り残したオレンジは次の年には落ちてしまうようにしましょう。
• 赤ちゃんドラゴンと会話が出来るようなプログラムを書いてみましょう。 feedやwalkのようなコマンドを入力できるようにして ドラゴンに対してそれらのメソッドが呼ばれるようにします。 もちろん、入力されたものは文字列なので、ある種のメソッドに転送する処理 をしなくてはならないでしょう。つまり、何の文字が入力されたかをチェックして適切な メソッドを呼び出すという処理です。
これでプログラムに関してはすべて完了です!! でもちょっと待ってください。 まだ、Eメールを送ったり、ファイルをコンピュータにしまったり読み出したり、 あるいは、ウインドウズやボタンを作ったり、はたまた3次元の世界を作ったり、 のようないろいろなことをするクラスについてまだ何も説明をしていませんでしたね。 はい、ここですべてを示すことが不可能な程たくさんの 使用可能な クラスがあります。そのほとんどは私でさえ知りません。 私が言えるのは その大部分がどこで見つけることが出来るかということです。 そうすればプログラムで使いたいクラスについて、ひとつずつ学ぶことが出来るはずです。 でも、あなたを送り出す前に、後ひとつの章を授けたいと思います。 それは、あなたが知っておくべきRubyのもうひとつの特徴で、ほかのほとんどの言語には 無いのだけれど、それ無しでは私は生きていけないくらいのものです。 それは、ブロックと手続きオブジェクトです。