これまで話していないRuby言語の構文と評価の詳細について述べる。完全な説
明をしようとは思っていないので、本書に登場しない細かい仕様は全てカット
した。だからこれだけ読んでもRubyプログラムが書けるようになるということ
は、まずありえない。完全なものが必要なら添付CD-ROMに同梱したリファレン
スマニュアル\footnote{Rubyリファレンスマニュアル:添付CD-ROMarchives/ruby-refm.tar.gz}を
読んでいただきたい。
Rubyを知っている読者はこの章も飛ばして構わない。
Rubyはリテラルを使った式の表現力が非常に高い。 Rubyをスクリプト言語たら しめているのは第一にトップレベルの存在、第二がリテラルの表現力だと筆者 は考えている。第三には豊富な基本ライブラリが挙げられるだろう。
リテラルは単体でも非常に強力だが、組み合わせるとさらに強力になる。特に ハッシュや配列を合成した複雑なリテラルを組み立てられるのがRubyのリテラ ルの最大の長所である。正規表現の配列のハッシュ、なんてものも素直に組み 立てていけば書ける。
具体的にどのような表現が許されるのか、順番に見てゆこう。
まず文字列と正規表現はスクリプトでは欠かすことはできまい。 文字列の表現はRubyのリテラルの中でも特に多彩である。
'string' # 「string」
'\\begin{document}' # 「\begin{document}」
'\n' # 「\n」バックスラッシュとn。改行ではない
'\1' # 「\1」バックスラッシュと1
'\'' # 「'」
一番シンプルな形式。Cではシングルクオートで囲ったものは文字になるが
Rubyではいずれも文字列である。これを「'文字列」と呼ぼう。バックスラッ
シュによるエスケープは「\」自体と「'」に対してのみ有効。他の文字にバッ
クスラッシュをつけても\はそのまま残る(四番目の例を参照)。
それから、Rubyの文字列は改行に分断されることはない。複数行に渡って 文字列を書いたら改行も含めて文字列として使える。
'multi
line
string'
またrubyコマンドに-Kオプションを付けるとマルチバイト文字列も
通るようになる。現在対応しているのはEUC-JP(-Ke)、
Shift JIS(-Ks)、UTF8(-Ku)の三つだ。
'「漢字が通る」と「マルチバイト文字が通る」はちょっと違う'
"string" # 「string」
"\n" # 改行
"\0x0f" # 16進数でのバイト表現
"page#{n}.html" # 式の埋め込み
クオートが変わると式展開と「バックスラッシュ記法」が使えるようになる。
バックスラッシュ記法とはCでもサポートされている古典的なやつで、\nが
改行を、\bがバックスペース、のような記法のことである。
Rubyではその他にも
Ctrl-CやESCも表現できて便利だ。しかしまあ、ただ列挙してもおも
しろくないし実装に関しても場合分けが増えるだけで特にすごいことがあるわ
けではない。よって全部まとめて省略する。
一方、式展開のほうはかなり凄い。「#{ }」の中に任意のRubyの式を書き、実
行時に評価した値を文字列として埋め込むことができる。変数一つとかメソッ
ド一つ、などという限定はない。こうなるともはや「リテラル」というよりは
全体が文字列を表す一つの式と言える。
"embedded #{lvar} expression"
"embedded #{@ivar} expression"
"embedded #{1 + 1} expression"
"embedded #{method_call(arg)} expression"
"embedded #{"string in string"} expression"
%文字列%q(string) # 'string'と同じ %Q(string) # "string"と同じ %(string) # %Q(string)と同じ、つまり"string"と同じ
文字列に区切り文字それ自体をたくさん入れようと思うと、エスケープがとて
も面倒だ。そういうとき、%を使うと区切り文字のほうを変更することができ
る。次に同じ文字列を「"」文字列と「%」文字列で書いた例を示す。
"<a href=\"http://i.loveruby.net#{path}\">"
%Q(<a href="http://i.loveruby.net#{path}">)
文字数は同じになってしまったものの、%文字列のほうが見通しがずっといい。
エスケープが増えれば短さでも%文字列のほうが有利だ。
ここでは区切り文字に(と)を使ったが、他のものでもいい。[と]でも、
{と}でも、#と#でも、記号系ならほぼなんでもいい。%でも構わない。
%q#this is string# %q[this is string] %q%this is string%
ヒアドキュメント(here-document)というのは行単位の文字列を表現できる
構文だ。普通の文字列は始まりの"から終わりの"の間にある文字が内容になる
が、ヒアドキュメントの場合は始まりの<<EOSがある行と終端のEOSの行の間に
ある行が内容になる。
"開始文字と終端文字の間にある文字が文字列。" <<EOS 開始行と終端行の 間にある行が ヒアドキュメント。 EOS
網かけにした部分がヒアドキュメントだ。
ここでは識別子にEOSを使ったが実際には英単語ならばなんでもよい。厳密に
言うと、[a-zA-Z_0-9]とマルチバイト文字が使える。
ヒアドキュメントに特徴的なのは、文字列が「開始記号・終端記号のある行」 で区切られるということだ。開始記号がある行が開始区切りだ。だから開始記 号の行内での位置は重要ではない。拡大解釈すると、たとえ式の途中だろうと なんだろうとどうでもいいということである。
printf(<<EOS, count_n(str)) count=%d EOS
この場合、「count=%d\n」という文字列が<<EOSの位置に値として渡される。
つまり以下と同じだ。
printf("count=%d\n", count_n(str))
開始記号の位置にはかなりいいかげんなのに対して終端記号のほうは厳密で、
行の先頭に置かなければならず、しかも行内に他の文字は一切置けない。ただし
次のように開始記号にマイナスを入れて<<-EOSにするとインデントだけはでき
るようになる。
<<-EOS
ヒアドキュメントの内容自体をインデントできたら
さらに便利だろうなあ。でもできないんだなあ。
そういうときは適当にメソッド書いてインデントを
消すのが定石です。ただしタブには注意。
EOS
また開始記号の識別子を""や''でくくることができ、それによって
文字列全体の性質が変わる。例えば<<EOSと<<"EOS"にすると
埋め込み式や各種バックスラッシュ記法が使用可能になる。
<<"EOS"
一日は#{24 * 60 * 60}秒に決まってます。
うそです。
EOS
では<<'EOS'なら'文字列と同じか、と思いきやそうではなく、「完全リテラ
ルモード」になる。つまりありとあらゆる文字列の機能が無効になり、一文字
たりとも変えずに文字列化する。バックスラッシュなどがよく出てくるソース
コード片を書くのに便利だ。
第二部ではヒアドキュメントのパース方法も解説するわけだが、 どうやったらパースできるか、読む前に考えてみてほしい。
Rubyの文字列はバイト列であり、文字オブジェクトのようなものはない。その かわり、次のような表現で文字に相当するASCIIコード(整数)を指定できる。
?a # 「a」に相当する整数 ?. # 「.」に相当する整数 ?\n # LF ?\C-a # Ctrl-a
/regexp/
/^Content-Length:/i
/正規表現/
/\/\*.*?\*\//m # Cのコメントにマッチする表現
/reg#{1 + 1}exp/ # /reg2exp/と同じ
スラッシュ二つで囲んだものが正規表現だ。正規表現というのは 文字列のパターンを指定する言語である。例えば
/abc/
という正規表現は、「aがあり、その次の文字がbで、その次の文字cで
ある文字列」に「マッチ」する。つまり"abc"や"fffffffabc"や
"abcxxxxx"にマッチする。
もっと特殊な並びを指定することもできる。
/^From:/
ならば、「行の始めがFromで次にコロンが来る文字列」にマッチする。
こういう表現が他にもいろいろあって、相当複雑なパターンも作る
ことができるようになっている。
使い道は無数にある。 マッチした部分を別の文字列に変換する、 マッチした部分を削る、 マッチするかどうか確かめる、などなど。
もっと具体的な用途なら、
メールからFrom:ヘッダの行を取り出す、
改行文字を\nから\rに変換する、
とある文字列がメールアドレスっぽいかチェックする、なんてことができる。
正規表現はそれ自体が独立した言語なので、rubyとは別に「正規表現のパーサ
と評価器」がある。rubyではregex.cがそれだ。だからrubyとしてはRubyプロ
グラムから正規表現部分を切り取って渡せればいい。結果として文法上の扱い
は文字列と同じようなものになっている。エスケープやバックスラッシュ記法、
式の埋め込みなど文字列にある機能は正規表現でもほとんどがそのまま使える。
ただし扱いが文字列と同じと言えるのはあくまで「Rubyの構文上は」という条 件が付くときだけである。先に述べたように正規表現はそれ自体が一つの 言語であるので、当然そちらの言語規約にも従っていなければならない。正規 表現について詳しく話しているとそれだけで一冊になってしまうのでそちらに 関しては他の本を読んでいただきたい。オライリーの『詳説正規表現』 \footnote{『詳説正規表現』Jeffrey E.F.Friedl著、歌代和正監訳、春遍雀來・鈴木武生共訳、オライリー・ジャパン、1999} がお勧めだ。
%正規表現
これまた文字列と同じだが、正規表現にも%を使った区切り変更構文があって、
%rが正規表現である。これは例を示すだけでいいだろう。
%r(regexp)
%r[/\*.*?\*/] # Cのコメントにマッチする表現(その2)
%r("(?:[^"\\]+|\\.)*") # Cの文字列にマッチする表現
%r{reg#{1 + 1}exp} # 式の埋め込み
[]でカンマ区切りのリストを囲ったのが配列リテラルである。
[1, 2, 3]
['This', 'is', 'an', 'array', 'of', 'string']
[/regexp/, {'hash'=>3}, 4, 'string', ?\C-a]
lvar = $gvar = @ivar = @@cvar = nil
[lvar, $gvar, @ivar, @@cvar]
[Object.new(), Object.new(), Object.new()]
Rubyの配列(Array)は任意のオブジェクトのリストだ。構文の点からすると
任意の式を要素にできるところが特徴的である。前述のように「正規表現のハッ
シュの配列」なんてものも簡単に作れるし、リテラルに限らず変数やメソッド
呼び出しを組み合わせた式も素直に書ける。
また他のリテラルと同じくこれも「配列オブジェクトを生成する式」である ことに注意。
i = 0 while i < 5 p([1,2,3].id) # 毎回違うオブジェクトIDを表示する i += 1 end
スクリプトを書くときは文字列の配列をよく使うので、文字列配列に
だけは特別な省略記法が用意されている。それが%wだ。例を見れば
一目瞭然である。
%w( alpha beta gamma delta ) # ['alpha','beta','gamma','delta']
%w( 月 火 水 木 金 土 日 )
%w( Jan Feb Mar Apr May Jun
Jul Aug Sep Oct Nov Dec )
式の埋め込みが使える%Wもある。これはかなり最近に実装された機能だ。
n = 5
%w( list0 list#{n} ) # ['list0', 'list#{n}']
%W( list0 list#{n} ) # ['list0', 'list5']
筆者にはいまだに%Wの使い道が思い付かない。
ハッシュテーブルは任意のオブジェクト同士の一対一関係を記憶できる データ構造だ。次のように書くとそれがテーブルを生成する式となる。
{ 'key' => 'value', 'key2' => 'value2' }
{ 3 => 0, 'string' => 5, ['array'] => 9 }
{ Object.new() => 3, Object.new() => 'string' }
# もちろん複数行にしてもいい
{ 0 => 0,
1 => 3,
2 => 6 }
ハッシュについては第3章『名前と名前表』で詳しく説明した。ハッシュ値によっ て記憶スロットを分散させる高速な検索テーブルである。Ruby言語の構文と してはキー・値ともに任意の式を使えるのが特徴だ。
さらに、メソッド呼び出しの引数では{...}なしで直接書くこともできる。
some_method(arg, key => value, key2 => value2)
# some_method(arg, {key => value, key2 => value2}) と同じ
この機能を使うと名前付き引数(キーワード引数)の真似事ができる。
button.set_geometry('x' => 80, 'y' => '240')
もちろんこの場合set_geometryはハッシュを受けるように書いておかないと
いけない。本物のキーワード引数なら言語がパラメータ変数に変換し
てくれるものだが、こちらはあくまで「もどき」なのでそうはいかない。
範囲リテラルは他の言語にはあまりない変わり種だ。
Rangeオブジェクトを生成する式である。
0..5 # 0から5まで(5を含む) 0...5 # 0から5まで(5を含まない) 1+2 .. 9+0 # 3から9まで(9を含む) 'a'..'z' # 文字列の'a'から'z'まで('z'を含む)
ドットが二つの場合は終端要素を含み、三つの場合は含まない。整数に限らず
小数の範囲とか文字列の範囲とか、やろうと思えば任意のオブジェクト同士の
範囲なんてものも作れる。ただそれは範囲オブジェクトのクラスたる
Rangeクラス(つまりライブラリ)の仕様であって文法の問題ではない。
パーサとしては任意の式を..で連結できるようにしてあるだけである。
もし評価した結果のオブジェクトで範囲が作成できないようなら実行時エラー
になる。
ところで、..と...の優先順位はかなり低いのでちょっと意外な解釈を
されることがある。
1..5.to_a() # 1..(5.to_a())
筆者はRubyの文法がかなり性に合うほうだと思うのだが、 ここの仕様だけはどうも好きでない。
シンボルについては第一部でさんざん話した。任意の文字列と一対一に対応す
る何かのことである。Rubyプログラムではこのシンボルを「:」を使って表す。
:identifier :abcde
これはかなり普通な例だ。実はこの他にも変数名・メソッド名
なら全て「:」をつけてシンボルにできる。例えばこのように。
:$gvar :@ivar :@@cvar :CONST
さらに、まだ話していないがメソッド名として[]やattr=なんてのも使え
るので、当然それもシンボルに使える。
:[] :attr=
配列の値にこういうシンボルを使ったりすると、見ためがかなり、ややこしい。
一番面白くないのがこれだ。強いて言うと一億を
1_0000_0000
というように途中でアンダーバーの区切りを入れて書けることだろうか。 それにしてもたいして面白いものではないので本書では数値のことは これ以後きれいさっぱり忘れることにする。
メソッドの定義と呼び出しについて話す。
def some_method( arg )
....
end
class C
def some_method( arg )
....
end
end
メソッドはdefで定義する。トップレベルで定義すれば関数風メソッドとなり、
クラス文内で定義すればそのクラスのメソッドとして定義される。
クラスに定義したメソッドを呼び出すには、
普通は次のようにnewでインスタンスを作成して呼ぶ。
C.new().some_method(0)
Rubyで定義したメソッドの値は、途中でreturnが実行されたときはその値。
それ以外のときは一番最後に実行された文の値が返る。
def one() # 1を返す
return 1
999
end
def two() # 2を返す
999
2
end
def three() # 3を返す
if true then
3
else
999
end
end
本体が空のときは自動的にnilになるし、
値のない式は定義の最後には置けないので、
返り値のないメソッドは作れない。
省略可能な引数というのも定義することができる。引数の数が足りない場合は パラメータが自動的にデフォルトの値になるのだ。
def some_method( arg = 9 ) # デフォルト値は9 p arg end some_method(0) # 0と表示する some_method() # デフォルト値の9を表示する
このような引数を複数とることもできる。ただしその場合は引数リストの最後 にまとめなければならない。リストの途中を省略可能にしてしまうと、引数が どう対応するのかわからなくなるからだ。
def right_decl( arg1, arg2, darg1 = nil, darg2 = nil ) .... end # こういうのはダメ def wrong_decl( arg, default = nil, arg2 ) # リストの途中は省略できない .... end
実はメソッド呼び出しの括弧は省略できる。
puts 'Hello, World!' # puts("Hello, World")
obj = Object.new # obj = Object.new()
Pythonでは括弧なしにするとメソッドオブジェクトが取れたりするのだが、 Rubyではそういうことはない。
引数の中でも省略できる。
puts(File.basename fname) # puts(File.basename(fname)) と解釈される
やりたければもっと省略してもよい。
puts File.basename fname # puts(File.basename(fname)) と解釈される
ただしさすがにこのような「省略のネスト」に対しては現在は警告が出るよ うになった。2.0では通らなくなりそうである。
実はさらに定義のときですらパラメータリストの括弧は省略できてしまう。
def some_method param1, param2, param3 end def other_method # 引数なし……これはよくある end
メソッド呼び出しの括弧を省略する人は多いが、定義の括弧を省略する人はあ まりいない。ただし引数がない場合だけは括弧ごと省略することが多いようだ。
引数はオブジェクトのリストであるからして、その逆にリスト(配列)を引数 として展開できてもおかしくはない。ようするにこういうことだ。
def delegate(a, b, c) p(a, b, c) end list = [1, 2, 3] delegate(*list) # delegate(1, 2, 3)と呼んだのと同じ
このように配列を引数として分配することができる。
この機能を「*引数」と呼んでおくことにしよう。
ここではわかりやすいようにローカル変数経由で示したが、もちろん
そんな制限はない。リテラルやメソッド呼び出しなども直接置ける。
m(*[1,2,3]) # これは最初から展開して書けばいいのだが…… m(*mcall())
*引数を普通の引数と混ぜることもできるが、
必ず普通の引数より後に書かないといけない。
パラメータ変数との対応が一意に定まらなくなるからだ。
定義のときはその逆に、不定個の引数を配列としてまとめて受けることが
できる。パラメータ変数の前に*をつければいい。
def some_method( *args ) p args end some_method() # []と表示される some_method(0) # [0]と表示される some_method(0, 1) # [0,1]と表示される
このように、余った引数を配列にまとめてくれる。*パラメータは一つしか宣
言できず、またデフォルト付き引数よりも後に置かなければならない。
def some_method0( arg, *rest ) end def some_method1( arg, darg = nil, *rest ) end
リストの展開とまとめ受けの機能を併用すれば、メソッドの引数を別のメソッ
ドにそっくりそのまま引き渡すこともできる。*パラメータの一番便利な使
いかたかもしれない。
# 引数をother_methodに引き渡すだけのメソッド def delegate(*args) other_method(*args) end def other_method(a, b, c) return a + b + c end delegate(0, 1, 2) # other_method(0, 1, 2)と同じ効果 delegate(10, 20, 30) # other_method(10, 20, 30)と同じ効果
機能が「メソッド呼び出し」というただ一つだけだからといって、その表現も 一つでなければいけないという必然性はない。巷で言う シンタックスシュガー(syntax sugar)である。 Rubyはそのシンタックスシュガーがてんこもりで、 パーサフェチにはたまらない。例えば次の例は全てメソッド呼び出しである。
1 + 2 # 1.+(2)
a == b # a.==(b)
~/regexp/ # /regexp/.~
obj.attr = val # obj.attr=(val)
obj[i] # obj.[](i)
obj[k] = v # obj.[]=(k,v)
`cvs diff abstract.rd` # Kernel.`('cvs diff abstract.rd')
慣れるまでは信じ難いと思うが、「attr=」「[]=」「`」などが(本当
に)メソッド名なのである。メソッド定義の名前欄にも書けるし、シンボルと
して使うこともできる。
class C def []( index ) end def +( another ) end end p(:attr=) p(:[]=) p(:`)
しかし甘いものなんて大嫌いだという人がよくいるのと同じで、シンタックス シュガーが嫌いな人も多いようだ。たぶん、本質的な意味が一つであるものを 見ためで胡麻化すのは卑怯な感じがするからだろう(みんな生真面目だね)。
以下、もう少し詳しく見ていく。
obj.name? obj.name!
最初はギャップの小さいものから。これは単に名前の最後に?や!が付けられる
だけ。呼ぶときと定義するときの見ために違いがないのであまり苦労しない。
それぞれ「これこれこういうメソッドを定義するときに使おう」という約束事
はあるが、それはあくまで人間レベルの約束事であって言語規約ではない。
このへんは手続き名に多彩な文字が使えるLisp系言語の影響だろう。
1 + 2 # 1.+(2)
二項演算子型。左辺のオブジェクトに対するメソッド呼び出しに変換される。
この例ならば1のメソッド+が呼ばれる。種類は以下に示すようにかなり多
い。+や-のような一般的な演算子から「等価」の==、Perl風の<=>な
んてものまで、盛り沢山だ。優先順位の高い順に並べる。
** * / % + - << >> & | ^ > >= < <= <=> == === =~
記号一つの&、|はメソッドだが二つになった&&と||は組み込み
演算子である。Cでの使われかたを思いだすといい。
+2 -1.0 ~/regexp/
単項演算子型。このタイプは+ - ~の三つしかない。+ -は見ため通りの働
きをする(ようにデフォルトでは定義されている)。単項の~は、
~regexpか~stringなら変数$_とのマッチ、~integerならばビット反転
になる。
また+と-は二項演算子にもあるので、区別するためにメソッド名やシンボ
ルでは+@、-@と書く。もちろん呼び出すときは単に+nや-nでよい。
obj.attr = val # attr=(val)
属性代入型。上記の呼び出しはattr=という名前のメソッド呼び出しに変換さ
れる。括弧を省略したメソッド呼び出しと併用すれば、以下のように
いかにも属性アクセスのようなふりをしたコードを書くこともできる。
class C def i() @i end # 実は定義は一行に書けたりする def i=(n) @i = n end end c = C.new c.i = 99 p c.i # 99と表示される
しかし実はどちらもメソッド呼び出しというわけだ。 Delphiのget/set propertyやCLOSのスロットアクセサの仕組みと似ている。
ちなみに、obj.attr(arg)=のように引数を取りつつ代入形式、という
メソッドは定義できない。
obj[i] # obj.[](i)
インデックス型。[]という名前のメソッド呼び出しに変換される。
配列やハッシュへのアクセスも実はこの仕組みを使って実装されている。
obj[i] = val # obj.[]=(i, val)
インデックス代入型。[]=という名前のメソッド呼び出しに変換される。
super
メソッドを単に置き換えるのではなく、既存のメソッドの動作に少し他の
ことを追加したい、ということはそれなりによくあるわけだ。そこでオーバー
ライドしつつもスーパークラスのメソッドを呼び出す仕組みが必要になる。
Rubyではsuperがソレだ。
class A
def test
puts 'in A'
end
end
class B < A
def test
super # A#testを起動する
end
end
RubyのsuperはC++やJavaとは違い、これ一語で「スーパークラスの同名のメソッ
ドを呼ぶ」という意味である。ちなみにsuperは予約語だ。
またsuperを扱うときは「引数ゼロのsuper」と「引数を省略したsuper」の違
いに注意してほしい。引数を省略したsuperはメソッドのパラメータ変数を
そのまま渡すようになる。
class A
def test( *args )
p args
end
end
class B < A
def test( a, b, c )
# 引数ゼロの super
super() # []と表示される
# 引数を省略した super。super(a, b, c) と同じ効果
super # [1, 2, 3]と表示される
end
end
B.new.test(1,2,3)
Rubyでは同じメソッドでも呼び出す場所(つまりオブジェクト)によって呼び 出せたり呼び出せなかったりする。こういう機能のことを普通は「可視性」 (見えるか見えないか)と言うのだった。Rubyでは以下の三つのタイプのメソッ ドが定義できる。
publicprivateprotected
publicメソッドはどこからでもどんな形式でも呼べる。
privateメソッドは「構文上で」レシーバなしの形式でなければ呼べない。
結果として、メソッドが定義されているクラスか
その下位クラスのインスタンスからしか呼べなくなる。
protectedメソッドは、メソッドが定義されているクラスか
その下位クラスのインスタンスだけが呼べる。
privateと違うのは別のインスタンスに対しても呼べることである。
C++と用語は同じなのに意味が微妙に違うので注意してほしい。
通常は以下のようにして可視性を制御する。
class C public def a1() end # publicになる def a2() end # publicになる private def b1() end # privateになる def b2() end # privateになる protected def c1() end # protectedになる def c2() end # protectedになる end
ここでpublicとかprivateは括弧を省略したメソッド呼び出しである。
予約語ですらない。
またpublicやprivateを引数付きで使うと特定のメソッドの可視性だけを
変えることもできる。が、こちらの使いかたは仕組みが面白くないので省略。
モジュールMがあったときに全く同じ内容で
M.method_nameM#method_name(可視性はprivate)の両方が定義されている場合にそれをモジュール関数と呼ぶ。
とは言えこの定義を聞いても存在価値がわからないと思う。使って嬉しい例を 見るとわかりやすい。
Math.sin(5) # 数回使うだけならこちらのほうが楽で独立性も高い include Math sin(5) # 何度も使うならこちらのほうが便利
両方のメソッドが同じ内容、というところがポイントだ。selfが違うのに一つ
のコードで同じ動きをするということは、インスタンス変数が非常に使いにく
いはずである。従ってそのようなメソッドは(sinのように)手続きだけを記
述したメソッドである可能性が高い。だからモジュール「関数」と呼ぶのだ。
イテレータと言ってもJavaやC++のイテレータクラスだとか
デザインパターンのIteratorとは少し違う。
正確に言うとあちらのイテレータは外部イテレータ、
Rubyのイテレータは内部イテレータと呼ばれるものだ。
これに関しては定義を説明してもまるでわかりにくいので
具体例で説明しよう。
arr = [0,2,4,6.8]
こんな配列がある。この配列の要素に順番にアクセスしたい。 C風の方法を使うなら次のように書くところだろう。
i = 0 while i < arr.length print arr[i] i += 1 end
イテレータを使うとこれが次のように書ける。
arr.each do |item| print item end
each do〜endまでがひとかたまりでイテレータメソッドの呼び出しである。
正確に言うと、eachがイテレータ(である)メソッドで、do〜endが
イテレータブロックだ。
|と|で囲んである部分はブロックパラメータと言い、
イテレータメソッドからブロックに渡ってくる引数を受ける変数になる。
やや抽象的に言うと、イテレータはコードの一部を切り取って渡すようなもの
である。この例を使って言うと「print item」の部分を切り取ってeachメソッ
ドに渡すことができる。そうするとeachが配列の要素を順番にその切り取った
コードに渡してくれるわけだ。
またその逆に、「print item」以外の部分を切り取ってeachというメソッドに
封入した、と考えることもできる。
i = 0 while i < arr.length print arr[i] i += 1 end arr.each do |item| print item end
Cでイテレータと一番似た働きをするのは関数ポインタを受ける関数、 つまり高階の関数だろう。だがRubyのイテレータとCで書いた高階の 関数とでは違うところが二点ある。
第一点は、Rubyのイテレータは一つしかブロックを取れないということ。例え ば次のようなことはできない。
# 間違い。複数のブロックは渡せない。 array_of_array.each do |i| .... end do |j| .... end
第二点は、Rubyのブロックは外側のコードとローカル変数を共有できることだ。
lvar = 'ok' [0,1,2].each do |i| p lvar # ブロックの外のローカル変数にアクセスできる end
ここがイテレータの便利なところである。
ただし変数を共有できるのはあくまでブロックの外側とだ。イテレータメソッ
ド(例えばeach)の中と共有するようなことはない。直感的に言うと、ソー
スコードの外見がつながっている場所のローカル変数だけしか見えない。
イテレータブロック内で初めて代入したローカル変数はそのブロックにローカル になる、即ちブロックローカル変数となる。何はともあれ使ってみよう。
[0].each do i = 0 p i # 0 end
とりあえずブロックを作るために、長さ1の配列に対してeachで繰り返して
おくことにする(ブロックパラメータは丸ごと省略してしまってもいいのだ)。
そのブロックの中で変数iに初めて代入した……即ち宣言した。
これでこのiはブロックローカルになる。
ブロックローカルと言うからにはブロックの外からはアクセスできない のだろう。試そう。
% ruby -e ' [0].each do i = 0 end p i # ここでエラーになる ' -e:5: undefined local variable or method `i' for #<Object:0x40163a9c> (NameError)
ブロックローカル変数をブロックの外で参照したら確かにエラーになった。 間違いなくブロックローカルになっているようだ。
イテレータはもちろん何重にもネストでき、 そのたびにブロックは新しいスコープを作る。
lvar = 0
[1].each do
var1 = 1
[2].each do
var2 = 2
[3].each do
var3 = 3
# ここではlvar, var1, var2, var3が見える
end
# ここではlvar, var1, var2が見える
end
# ここではlvar, var1が見える
end
# ここではlvarだけが見える
ここで注意しなければいけない点が一つある。現在メジャーな言語と
違ってRubyのブロックローカル変数はshadowingをしないのだ。
shadowingとは、例えばCで言うと以下のコードで宣言されている
二つのiが別物だという話だ。
{
int i = 3;
printf("%d\n", i); /* 3 */
{
int i = 99;
printf("%d\n", i); /* 99 */
}
printf("%d\n", i); /* 3 (元に戻った) */
}
内側のブロックの中にいる間はあたかも内側のiが外側のiを覆い隠して
いるかのようだ。その「覆い隠すこと」をshadowingという。shadowは影だか
ら、自分の影の中に入れて(その結果隠して)しまう、という意味である。
ではshadowingをしないRubyのブロックローカル変数の場合はどうなるか。 次の例を見てほしい。
i = 0 p i # 0 [0].each do i = 1 p i # 1 end p i # 1(変更されたまま)
ブロックの内側でiに代入しても外側に同じ名前があればそれを使う。
だから内側でiに代入すれば外側のiの値が変わる。
この点に関しては何度も何度も「間違いやすいからshadowingしてくれ」
という文句が出てそのたびにフレーム寸前になるのだが、
いまだに結論は出ていない。
いくつか残った細かい話をする。
まず、イテレータには書式が二種類ある。いままで使ってきたdo〜endが一つ。
もう一つは{〜}を使う表現だ。以下の二つは全く同じ意味である。
arr.each do |i|
puts i
end
arr.each {|i| # {〜}イテレータはインデント4にするのが筆者の趣味
puts i
}
だがこの二つの書式では文法上の優先順位が違う。{〜}ブロックのほうが
ずっと優先順位が高い。
m m do .... end # m(m) do....end
m m { .... } # m(m() {....})
それから、イテレータもやはりメソッドであることに違いはないので、 引数をとるイテレータメソッドというのも当然ある。
re = /^\d/ # 「行の一文字目が数字」にマッチする正規表現 $stdin.grep(re) do |line| # 一文字目が数字である行に対して繰り返す .... end
yield
もちろんユーザが自分の好きなイテレータを定義することもできる。本体で
yieldを使っているメソッドがイテレータだ。試しにArray#eachと同じ効果を
持つイテレータを自分で書いてみよう。
# クラスArrayに定義を追加
class Array
def my_each
i = 0
while i < self.length
yield self[i]
i += 1
end
end
end
# こちらはオリジナルのeach
[0,1,2,3,4].each do |i|
p i
end
# 使いかたも効果も同じ
[0,1,2,3,4].my_each do |i|
p i
end
yieldがブロックの呼び出しだ。その時点で渡されたブロックに制御が移り、
ブロックの実行が終われば同じ場所に戻ってくる。特殊な関数呼び出しみたい
なものだと思えばいい。もし現在のメソッドがブロック付き呼び出し(イテレー
タ)でなければ実行時エラーになる。
% ruby -e '[0,1,2].each'
-e:1:in `each': no block given (LocalJumpError)
from -e:1
Procイテレータはコードを切り取って引数として渡すようなものだ、と話した。 だが実はもっと直接的に、コードをオブジェクトにして持ち運ぶことも できる。
twice = Proc.new {|n| n * 2 }
p twice.call(9) # 18と表示される
ようするに関数みたいなものだ。newを使って生成していることからも
想像できるとおり、Proc.newの返り値はProcクラスのインスタンスである。
Proc.newの見ためは明らかにイテレータだし、実際その通りである。
これは普通のイテレータだ。ただちょっとProc.newの内部でゴソゴソやって、
イテレータブロックをオブジェクト化するような仕掛けを動かしているに
すぎない。
ちなみにProc.newと同じ効果のlambdaという関数風メソッドも用意されて
いる。好みで選んでもらえばよい。
twice = lambda {|n| n * 2 }
Proc
なぜ突然Procの話を始めたかと言うと、イテレータとProcにはとても深い関係
があるからだ。実のところイテレータブロックとProcオブジェクトとは「同じ
もの」なのである。だから相互に変換可能だ。
まずイテレータブロックをProcオブジェクトにするには
パラメータ名の前に&を付ければよい。
def print_block( &block ) p block end print_block() do end # #<Proc:0x40155884>のように表示される print_block() # ブロックなしなのでnilと表示される
引数名の前に&を付けると、ブロックがProcオブジェクトに変換されその変数
に代入される。メソッドがイテレータでないとき
(ブロック付き呼び出しではないとき)にはnilが入る。
そしてその逆、Procをイテレータブロックとして渡すにもやはり&を使う。
block = Proc.new {|i| p i }
[0,1,2].each(&block)
このコードは以下と全く同じ意味を持つ。
[0,1,2].each {|i| p i }
この両方を組み合わせると、イテレータブロックを 他所のメソッドに丸投げすることもできるようになる。
def each_item( &block ) [0,1,2].each(&block) end each_item do |i| # [0,1,2].each do |i|と同じこと p i end
Rubyで言う「式」とは、「他のものと組み合わせて別の式や文を作れる」もの のことである。例えばメソッド呼び出しはまた別のメソッド呼び出しの引数 にしたりできるので式である。各種リテラルも同様だ。ただしリテラルやメソッ ドが必ずしも他の要素の組み合わせでないのに対して、これから紹介する「式」 は必ず組み合わせからなる。
if
if式は説明不要だろう。条件式が真のとき本体を実行する。なお第一部で説明
したようにRubyでは「nil/false以外の全オブジェクト」が真である。
if cond0 then .... elsif cond1 then .... elsif cond2 then .... else .... end
elsif・else節は省略可。各thenも省略可。ただthenのあたりに
関してはもうちょっと細かい条件がある。こういうものは例を見る
のに限るので、以下の記述が全て通る、とだけ言っておこう。
# 1 # 4
if cond then ..... end if cond
then .... end
# 2
if cond; .... end # 5
if cond
# 3 then
if cond then; .... end ....
end
またRubyではifは式なので、if式全体の値というものがある。
それは条件式が合致した本体の値である。例えば最初のifの条件が
真ならその本体の値だ。
p(if true then 1 else 2 end) #=> 1 p(if false then 1 else 2 end) #=> 2 p(if false then 1 elsif true then 2 else 3 end) #=> 2
マッチする節がなかったり、マッチした節が空だったりしたときは
nilになる。
p(if false then 1 end) #=> nil p(if true then end) #=> nil
unless
ifと条件を逆にしたのがunlessだ。
以下の左右の式は全く同じ意味になる。
unless cond then if not (cond) then .... .... end end
unlessにもelseは付けられるがelsifは付けられない。
thenが省略できるのは言うまでもなし。
unlessにも値があり、その決定条件はifと全く同じである。つまり条件が
マッチした節の本体の値が全体の値になる。どの節にもマッチしないかマッチ
した節が空のときはnilになる。
and && or ||
andの一番ありがちな使いかたと言えばブール演算だろうか。
例えばifの条件式で使ったりする。
if cond1 and cond2 puts 'ok' end
だがPerlやshやLispと同じように、条件分岐構文として使うこともできる。
次の左右の式は同じ意味だ。
if invalid?(key)
invalid?(key) and return nil return nil
end
また&&とandは同じ意味である。違うのは結合順位だ。
method arg0 && arg1 # method(arg0 && arg1) method arg0 and arg1 # method(arg0) and arg1
基本的に、記号系演算子は引数になれる式(arg)を作る。
アルファベット系演算子は引数になれない式(expr)を作る。
一方のorはandの逆である。つまり、左辺の評価値が偽であるときに右辺も
評価する。
valid?(key) or return nil
そしてorと||の関係はandと&&の関係と同じだ。優先順位だけが違う。
Cのような条件演算子もある。
cond ? iftrue : iffalse
記号のまわりのスペースは重要だ。くっつけてしまうと次のように妙なことが 起こる。
cond?iftrue:iffalse # cond?(iftrue(:iffalse))
条件演算子の値は、式で最後に実行された式の値である。 つまり真側か偽側、いずれかの式の値だ。
while until
while式。
while cond do .... end
最も単純なループ構文である。condが真のあいだ本体を実行する。
whileのdoも省略できる。
until io_ready?(id) do sleep 0.5 end
untilは条件判断が逆のループだ。条件が偽のあいだ本体を実行する。
untilのdoも省略できる。
それともちろんループから抜けたりするためのジャンプ構文も存在する。
C/C++/Javaで言うbreakはそのままbreak。continueはnext。
nextはPerl由来だろうか。
i = 0
while true
if i > 10
break # ループを抜ける
elsif i % 2 == 0
i *= 2
next # 次のループ
end
i += 1
end
さらにこれもPerl由来だろうが、redoというのがある。
while cond # (A) .... redo .... end
と書くと、(A)の地点まで戻ってその繰り返しをやりなおす。
nextと違うのは条件をチェックしないことだ。
筆者は一生で書いたRubyプログラムの量なら世界で二桁の中には入る自信があるが、
redoなんて一度も使ったことがない。それでも幸せに生きていられるというこ
とは、たぶんさほど必要ないものなんだろう。
case
if式の特殊形。連続した条件分岐を行う。
次の左右の式は全く同じ意味だ。
case value when cond1 then if cond1 === value .... .... when cond2 then elsif cond2 === value .... .... when cond3, cond4 then elsif cond3 === value or cond4 === value .... .... else else .... .... end end
イコールが三つの「===」は「==」と同じく実際にはメソッド呼び出しで
ある。左辺の値がレシーバ(メソッドを呼ぶオブジェクト)になることに注意
しよう。具体的な効果としては、Arrayの===なら要素にvalueが含まれ
るかを試す。Hashならキーにvalueがあるかどうか試す。正規表現なら
valueとのマッチを試す。などなど。caseは文法の要素数が多くて例にす
るには面倒なので本書では扱わない。
メソッドの壁を越えてエラーを伝達する制御構造。C++やJavaを御存知の 読者ならば例外というもの自体についてはわかっているだろう。Rubyの 例外もそれとだいたい同じである。
Rubyでは例外は関数風メソッドraiseで発生させる。
raiseは予約語ではない。
raise ArgumentError, "wrong number of argument"
Rubyでは例外はExceptionクラス(またはその下位クラス)のインスタンスだ。
この形式では例外のクラスを第一引数に、エラーメッセージを第二引数に指
定している。するとraiseはArgumentErrorのインスタンスを作成し、それを
「投げる」。例外オブジェクトはraiseの後にあったコードをすっとばしてメ
ソッド呼び出しのスタックを戻りはじめる。
def raise_exception raise ArgumentError, "wrong number of argument" # このあとのコードは実行されない puts 'after raise' end raise_exception()
何も遮るものがなければ例外オブジェクトはどこまでも進みついにはトップ
レベルまで戻ってしまう。戻るところがなくなるとrubyはメッセージを出して
非ゼロ終了する。
% ruby raise.rb
raise.rb:2:in `raise_exception': wrong number of argument (ArgumentError)
from raise.rb:7
しかしそれだけならexitで十分なのであって、例外というからには
ハンドラを設定できなくてはいけない。Rubyではbegin〜rescue〜endで
それを行う。C++やJavaで言うところのtry〜catchである。
def raise_exception raise ArgumentError, "wrong number of argument" end begin raise_exception() rescue ArgumentError => err then puts 'exception catched' p err end
rescueは例外を捕らえる制御構造で、指定したクラスと、その下位クラスの例
外オブジェクトを捕らえる。この場合ArgumentErrorを捕らえろと命令したと
ころに丁度よくArgumentErrorのインスタンスが飛んできたのでこのrescueに
マッチする。すると=>errによって例外オブジェクトがローカル変数errに
代入され、このrescue節が実行される。
% ruby rescue.rb exception catched #<ArgumentError: wrong number of argument>
例外がrescueされると何もなかったかのようにrescueの後ろに抜けて
続きを実行しはじめるわけだが、
beginからやりなおさせることも可能である。それにはretryを使う。
begin # ここに戻る .... rescue ArgumentError => err then retry # 人生やりなおしてきなさい end
なおrescueの=>errとthenは省略してよい。また捕らえる例外クラスも省略で
き、そのときはStandardErrorを指定したのと同じことになる。
もっとたくさんの種類の例外を補足したいときは例外クラスを並べて書けばい
い。また例外によって処理を変えたいときはrescue節自体を複数指定すればい
い。
begin raise IOError, 'port not ready' rescue ArgumentError, TypeError rescue IOError rescue NameError end
こう書くと上から順番にクラスがマッチするrescue節を探し。マッチした節だ
けが実行される。つまりこの場合はIOErrorの節だけが実行される。
その逆に、else節を追加しておくと
例外が発生しなかった場合にだけその節が実行される。
begin nil # もちろん例外は起きない rescue ArgumentError # 例外は起きないのでここは通らない else # 例外が起きないのでここが実行される end
さらにensure節を追加すると今度は例外が起きた場合も起きない場合も
rescueされたときも、その節が必ず実行される。
begin
f = File.open('/etc/passwd')
# いろいろする
ensure # 例外が起きても起きなくても常に実行される
f.close
end
ところで、このbegin式にも値がある。begin〜end全体の値は、
begin・rescue・elseいずれかの節のうち最後の通った節の最後の文の値である。
つまりensure以外の節のうち最後の文だ。ensureが除外されているのは、
ensureはだいたい後始末のために使われる(本処理ではない)からだろう。
変数および定数の参照。値は変数の指すオブジェクト。 それぞれの挙動は既に詳しすぎるほど説明したので省略する。
lvar @ivar @@cvar CONST $gvar
一点だけ追加。$で始まる変数には特殊な種類のものが存在する。変な名前
のものが存在するうえに、グローバル変数であるとも限らない。
まずPerl由来の$_と$~。$_はgetsなどの返り値を保存する
変数で、$~は最後の正規表現マッチの結果を保持する。この二つはローカル
変数にしてスレッドローカルというとんでもない変数である。
また例外が発生したとき例外オブジェクトを保持する$!や、
子プロセスのステータスを保持する$?、セキュリティレベルを表す
$SAFEはスレッドローカルである。
変数への代入は全て=で行う。全ての変数は型無しで、保持するのは
オブジェクトへのリファレンスである。実装としてはVALUE(ポインタ)
であった。
var = 1
obj = Object.new
@ivar = 'string'
@@cvar = ['array']
PI = 3.1415926535
$gvar = {'key' => 'value'}
ただし先程話した通りobj.attr=valみたいのは代入ではなくメソッド呼び出
しである。
var += 1
C/C++/Javaにもあるこの構文。これはRubyでは
var = var + 1
のショートカットである。Cと違うのは、+はRubyではメソッドなのでライブ
ラリで実行されるという点だ。Cでは+=全体の意味が言語処理系に組み込ま
れていた。またC++では+=や*=全体をオーバーライドできたがRubyではで
きない。+=ならば常に+と代入の組み合わせ操作として定義される。
自己代入と属性アクセス風メソッドを組み合わせることもできる。 こうするとさらに属性っぽい。
class C def i() @i end # defは一行でも書けるのだ def i=(n) @i = n end end obj = C.new obj.i = 1 obj.i += 2 # obj.i = obj.i + 2 p obj.i # 3
+=があるなら++もあるのかな、と思いきや++はない。なぜだろうか。
Rubyでは代入は言語処理系が扱うものである。一方メソッドはライブラリが実
行するものである。この二つ、即ち変数世界とオブジェクト世界、をきっ
ぱり分けるというのはRubyの重要な特徴である。++を導入するとその区別が
壊れてしまいかねない。というのが++のない理由だ。
それでも++の簡潔さが捨てきれない人は多いらしく、メーリングリストで何度
も何度も提案されては却下され続けてきた。筆者もわりと++欲しい派なのだが、
我慢できないほどではないし、Rubyではそもそも++はあまり使わないので
黙って忘れることにしている。
defined?
defined?はRubyの中ではかなり異色の構文である。
実行時に式の値が「定義」されているかどうか判別する。
var = 1 defined?(var) #=> true
言い換えると、引数(と言っていいのかどうか?)に受けた式を評価したとき 値を得られるかどうかを判別する。とは言ってももちろんパースエラーになる 式は書けないし、メソッド呼び出しの中で例外が発生したりするものは 検出できない。
defined?についてはぜひやりたかったのだが、
本書では以後いっさい出てこない。残念だ。
基本的に他の構文と組み合わせられない、 つまり縦に並べて書くものが文である。
かと言って評価値がないわけではない。例えばクラス定義文にも実は値があ るし、メソッド定義文の値というのもある。ただそれを使うことはあまり推奨 されていないし、役に立たない、というくらいに軽くとらえておくのがよい。 ここでもそれぞれの文の値については省略する。
これまでは「とりあえず一行が一文」と言ってきた。しかしRubyの文終端は そんなに単純なものではない。
まず(Cのように)セミコロンを置いて明示的に終端できる。 当然セミコロンを使ったときは一行に二文以上書くこともできる。
puts 'Hello, World!'; puts 'Hello, World once more!'
その一方で、開き括弧や二項演算子、カンマの後など 「式が明らかに継続している」場合は自動的に文が継続する。
# 1 + 3 * method(6, 7 + 8)
1 +
3 *
method(
6,
7 + 8)
かと言ってバックスラッシュで行の継続を明示するのもそれはそれで一向に構 わない。
p 1 + \ 2
if/unless修飾子
if修飾子は通常のifの変形版である。
以下の左右のプログラムは全く同じ意味だ。
on_true() if cond if cond
on_true()
end
unless修飾子はその否定版。
ガード文(例外条件を排除する文)を書くのに便利だ。
while/until修飾子
whileとuntilにも後置記法がある。
process() while have_content? sleep(1) until ready?
これとbegin〜endを組み合わせるとCのdo〜whileループになったりする。
begin res = get_response(id) end while need_continue?(res)
class C < SuperClass .... end
SuperClassを継承したクラスCを定義する。
クラスについては第一部でかなりしつこく説明した。実行される文であること、
文内では定義中のクラスがselfになること、本体には任意の式が書けること、
クラス定義文はネストできること。いずれもRubyの実行イメージの根幹をなす。
def m(arg) end
メソッドの定義は既に書いたので略。 ここに入れたのは文の仲間だということを明示したかっただけだ。
特異メソッドについては第一部でさんざん説明した。クラスでなくオブジェク トに所属するメソッドのことで、実は特異クラスというクラスに定義されるの だった。定義方法はメソッド名の前にレシーバを書くだけだ。パラメータ宣言 でも通常のメソッドと同じ表記が全て使える。
def obj.some_method end def obj.some_method2( arg1, arg2, darg = nil, *rest, &block ) end
class << obj .... end
目的の観点からすると、特異メソッドをまとめて定義するための文。
手段の観点からすると、文の実行中、objの特異クラスがselfになる文。
Rubyプログラムにおいて特異クラスが露出するのは唯一ここだけである。
class << obj p self #=> #<Class:#<Object:0x40156fcc>> # 特異クラス「(obj)」 def a() end # def obj.a def b() end # def obj.b end
複数の代入をまとめて行うのが多重代入である。例えば一番簡単な例は次の ようなものだ。
a, b, c = 1, 2, 3
これは以下と全く同じである。
a = 1 b = 2 c = 3
だが短く書けるというだけでは何もおもしろくない。実は配列をからめると 初めて楽しくなってくるのである。
a, b, c = [1, 2, 3]
これも前の例と同じ結果になる。
またさらに、右辺は文法的なリストやリテラルに限られているわけではない。 変数やメソッド呼び出しでもいい。
tmp = [1, 2, 3] a, b, c = tmp ret1, ret2 = some_method() # 複数の返り値を返しているように見える
厳密に言うと次のようになる。左辺の評価値(のオブジェクト)をobjと置くと、
objが配列であればそれを使うobjにto_aryメソッドが定義されていればそれで配列に変換する[obj]を使うこの手順に従って右辺を決定し、代入を行う。つまり右辺の評価と代入の操作は 完全に独立している。
まだ先がある。実は左辺・右辺とも無限にネストできる。
a, (b, c, d) = [1, [2, 3, 4]] a, (b, (c, d)) = [1, [2, [3, 4]]] (a, b), (c, d) = [[1, 2], [3, 4]]
このプログラムでは各行を実行後、a=1 b=2 c=3 d=4になる。
まだまだある。左辺にはインデックス代入や属性代入も可能。
i = 0 arr = [] arr[i], arr[i+1], arr[i+2] = 0, 2, 4 p arr # [0, 2, 4] obj.attr0, obj.attr1, obj.attr2 = "a", "b", "c"
メソッドのパラメータのように、*を使ってまとめ受けできる。
first, *rest = 0, 1, 2, 3, 4 p first # 0 p rest # [1, 2, 3, 4]
一度に全部使うともう、何がなんだかわからない。
イテレータのところでは軽く流してしまったブロックパラメータだが、 実は多重代入と深い関係がある。例えば以下のような場合、
array.each do |i| .... end
ブロックが呼ばれるたびにyieldされた引数がiに多重代入されているのである。
ここでは左辺が変数一つだけなので多重代入に見えないのだが、二つ以上にす
るとちょっと見えてくる。例えばHash#eachはキーと値の組に対する繰り返しな
ので、普通はこのように呼ぶ。
hash.each do |key, value| .... end
この場合、実はキーと値の配列がハッシュからyieldされている。
そういうわけだから、ネストした多重代入を使って次のようなこともできる。
# [[キー,値],インデックス]がyieldされている hash.each_with_index do |(key, value), index| .... end
aliasclass C alias new orig end
既に定義してあるメソッドorigと同じ本体を持つメソッドnewを定義する。
aliasは別名というよりはUNIXのファイルシステムで言うハードリンクに似て
いる。つまり一つのメソッド本体に複数の名前を付ける手段である。逆に言う
と名前自体はそれぞれ独立しているので、片方のメソッドが下位クラスでオー
バーライドされたとしてももう片方は元の動きのまま残る。
undefclass C undef method_name end
メソッドC#method_nameの呼び出しを禁止する。単純に「定義を取り消す」
のではない。スーパークラスにメソッドがあったとしても呼べない。つまり
メソッドの代わりに「このメソッドは呼び出し禁止」という標識を立てるよう
なものだ。
undefは超強力で、一度undefしたらRubyレベルからは絶対に取り消せない。
内部構造の矛盾を隠すのに使っているからである。どうにかするには
継承して下位クラスでメソッドを定義するしかない。その時でもsuperを
呼ぶとエラーになる。
ちなみにファイルシステムで言うunlinkに相当するのは
Module#remove_methodというメソッドである。クラス定義中はselfが定義
中のクラスだったから、次のように呼び出せる(クラスはモジュールの下位ク
ラスだというのも思い出しておこう)。
class C remove_method(:method_name) end
ただしこのremove_methodでもやっぱりundefは消せない。
undefで立てた標識はあらゆる類の検索を停止させるからだ。
# examples of bad comments. 1 + 1 # compute 1+1. alias my_id id # my_id is an alias of id.
#から行末まではコメントである。
プログラムにとっては何の意味も持たない。
=begin 埋め込みドキュメントです。 プログラムに埋め込まれているドキュメントだからそう呼びます。 なんの工夫もないですね。 =end
文字列などの外の、行頭に置かれた=beginから=endまでは
埋め込みドキュメントである。
中身の使いかたは自由だ。プログラムからは単なるコメントとして
読み飛ばされる。
グローバル変数$KCODEが"EUC"・"SJIS"・"UTF8"のいずれかに
なっているとデータの文字列の中で
euc-jpやshift_jisやutf8の文字列を使うことができる。
さらにrubyコマンドに-Ke・-Ks・-Kuのどれかの
オプションを付けると、
コード中にすらマルチバイト文字列を使うことができるようになる。例えば
文字列リテラルや正規表現リテラル、さらには識別子にさえも使える。だから
こんなことをしてもよい。
def 表示( arg ) puts arg end 表示 'にほんご'
しかしこういうことをやるのは全くお勧めできない。
御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。
『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。
Copyright (c) 2002-2004 Minero Aoki, All rights reserved.