遠藤です。
Rubyでやらせたいことが特にある商売ではないので、私にとってRubyは
手段ではなく、目的です。
前田さんがfaqをまとめてくださっていますので、重複を避けつつ、一部
自分にとっては分かりやすい表現に直したりもして、traps & tipsとして
とりあえずまとめてみました。過去メールから拾ったネタが結構多いの
ですが、引用していません。悪しからず。自分ではまったもの、なかなか
理解できなかったものも多く含まれています。
勘違いがあると思いますので、ご批判をお待ちしています。
Ruby本が出るまでの暫定版です。厚そうで沢山目からうろこが落ちるの
ではないかと今から楽しみです。
ちょっと長いのですが、tar.gzで読める人だけでないのですみません。
あまり長くなると読む気がしなくなる、というのも真実ですので、
perlfaqのように分けるか、manを作るか、いずれにせよ英訳しないと
いけないですね。ruby-talkへ行ってみますか。
0.メタ質問
Q0-1)マニュアルを読んでも理解できないところがあるのですが
Rubyは、基本的な構文はRuby1.0以来大きくは変わっていませんが、絶えず
拡張、修正が行われていますので、ドキュメントが最新バージョンに追い
付いていないところがあります。また、ソースがドキュメントだという説も
あります。
分からなくなったら、遠慮なくruby-listで質問すると、教祖まつもとさんを
はじめ、尊師の方々や私もはまったという人たちに分かりやすく教えて
いただけます。
質問をするには、プラットホームとrubyのバージョン、はまったスクリプト
(長い場合は本質的なところを切り出して)を示せばよいでしょう。irbを
使っている場合は、irb固有の問題もありますので、irb --single-irbで
試してみるか、rubyで実行し直して確認することをおすすめします。
MLを検索すれば、かなりの疑問が解決するとは思いますが、メールも
大量になってしまって、ありふれた検索では絞り込みにくくなっています。
最近のものくらいはチェックしておくのがネチケットというものだとは
思いますが、言うは易く、行うは難しですし、新しい視点も生まれるかも
しれません。思い切って質問してみましょう。
1.文法(変数、定数)
Q1-1)ローカル変数のスコープはどのように決められていますか
トップレベル、クラス(モジュール)定義、メソッド定義、ブロックの
それぞれで独立したスコープになっています。ブロック内では、外側の
ローカル変数を参照できます。
ブロック内が特別になっているのは、Threadや手続きオブジェクトの
中で局所化するためです。
while、until、forは制御構造であり、新しいスコープを導入しません。
loopはメソッドで、後ろについているのはブロックです。
Q1-2)ローカル変数はいつ参照可能になるのでしょうか
rubyスクリプトは、rubyインタープリータに実行させようとすると、
まず最後まで一度読みこまれ構文解析されます。構文上問題が生じなければ、
構文解析で作られた構文木が最初から実行に移されます。
ローカル変数が参照可能になるのは、この構文解析の時にローカル変数への
代入文が見つかった時です。
for i in 1..2
if i == 2
print a
else
a = 1
end
end
test.rbというファイルにいれて、このスクリプトを実行すると、
test.rb:3: undefined local variable or method `a' for
#<Object:0x40101f4c> (NameError)
from test.rb:1:in `each'
from test.rb:1
ということで、iが1の時は、エラーが起こらず、iが2になった時に
エラーが起こります。構文解析の時には、最初のprint aが実際には
aへの代入が行われてから実行されるというところまで解析されず、
この文を構文解析する時までにaへの代入文が現われていないので、
ローカル変数は参照されません。実行時にはaというメソッドが
ないか探しますが、これも定義されていないのでエラーになります。
逆に、次のスクリプトは、エラーになりません。
a = 1 if false; print a
==>nil
ローカル変数のこのような振舞いに悩まされないためには、ローカル変数が
参照される文より前に、a = nil といった代入文を置くことがすすめられて
います。こうすると、ローカル変数の参照が速くなるというおまけもついて
います。
Q1-3)定数のスコープはどのように決められていますか
クラス定義(モジュール、トップレベルを含む)ごとに独立したスコープに
なっています。サブクラスからは直接参照できます。
Q1-4)変数や定数にメソッドを適用すると予期しないことが起こりますが
次のような例でしょうか。
A = a = b = "abc"; b << "d"; print a, " ", A
==>abcd abcd
変数や定数への代入は、オブジェクトを後でその変数や定数で参照する
ために用いられます。変数や定数にオブジェクトそのものが代入されて
いるのではなく、オブジェクトの参照を保持しているだけです。変数は、
この参照を変更して異なるオブジェクトを参照するようにすることが
できますが、定数では一度保持した参照を変更することができません。
変数や定数にメソッドを適用すると、そのメソッドは、変数や定数が
指しているオブジェクトに適用されます。上の例では、<<というメソッドが
オブジェクトの状態を変えてしまうために、「予期せぬ」結果が生まれて
います。オブジェクトが数値の場合には、数値の状態を変えるメソッドが
ないため、このような問題は生じません。数値にメソッドを適用した時には
新しいオブジェクトが返されます。
この例では、文字列で示しましたが、配列やハッシュなど、オブジェクトの
状態を変更するメソッドを持っているオブジェクトでも同様のことが起こり
得ます。
Q1-5)Fixnum、true、nil、falseが即値だということですが、参照との違いは何ですか
特異メソッドを定義できなかったり、インスタンス変数を持てないなどの
差はありますが、これらのクラスでは破壊的メソッドがありませんので、
参照との差を感じることはないと思います。
Q1-6)定数は変更されませんか
定数があるオブジェクトを指しているとき、別のオブジェクトを指すように
はできませんが、そのオブジェクトが破壊的メソッドを持っていれば、
オブジェクトの内容は変更できます。
Q1-7):exitというのは何ですか
シンボルと呼ばれる、変数と1対1対応する整数(Fixnum)です。
Q1-8)オブジェクトのインスタンス変数を参照できますか
直接参照することはできませんが、メソッドを介して値を知ったり、
値をセットしたりすることができます。Module#attr、Module#attr_reader、
Module#attr_writer、Module#attr_accessorを参照してください。
もちろん、自分でメソッドを定義して参照することもできます。
method=の形のメソッド定義は、methodと=の間に空白をいれることが
できませんが、メソッド呼出しの時には、空白をいれることが可能ですし、
+、-などのメソッドが定義されていれば、+=、-=などの自己代入も行えます。
2.文法(メソッド、イテレータ)
Q2-1)privateとprotectedの違いが分かりません
privateの意味は、メソッドを関数形式でだけ呼び出せるようにし、
レシーバー形式では呼び出せないようにするという意味です。したがって、
可視性がprivateなメソッドは、自クラス及びサブクラスからしか参照
できません。
protectedも同様に、自クラス及びサブクラスからしか参照できませんが、
関数形式でもレシーバー形式でも呼び出せます。
モジュール関数は、privateとしての性質とモジュールの特異メソッド
としての性質を合わせ持つもので、includeまたはextendすることにより、
関数形式で呼び出すことが可能です。もちろん、特異メソッドの形で呼び
出すことも可能です。
Q2-2)イテレータというのは何ですか
ブロックや手続きオブジェクトを与えられたメソッドをイテレータと呼びます。
直接メソッドの後ろに{}でブロックを置く方法と、手続きオブジェクト(を
指す変数、定数)の前に&をつけて引数として渡す方法があります。
Q2-3)メソッドに与えられたブロックはどのように使われますか
メソッドの中からブロックを実行するには、yield制御構造、ブロック引数、
Proc.newの3種類の方法で行うことができます。
yieldの場合には、yieldの後ろに続く引数が、ブロックパラメータと
してブロックに渡され、ブロックが実行されます。
ブロック引数は、メソッド定義の引数の最後に&methodという形で置かれ、
メソッドの中で、method.call(args...)という形で呼ばれます。
Proc.newは、メソッドの中で使われたときには、引数としてそのメソッドに
渡されたブロックをとり、そのブロックを内容とする手続きオブジェクトを
生成します。procまたはlamdaも同様です。
def a (&b)
yield
b.call
Proc.new.call
proc.call
lambda.call
end
a{print "test\n"}
Q2-4)ブロックに引数を渡すにはどうしますか
ブロックの先頭に、仮引数を||で囲って置くと、実引数が多重代入されます。
Q2-5)Proc.newでは手続きオブジェクトが作られませんが
Proc.newは、ブロックを与えないとエラーになります。メソッド定義の中で
使われるブロックなしのProc.newは、メソッド呼出しにブロックが与えられて
いることを仮定しています。
Q2-6)メソッドの引数は参照渡しですか
参照渡しです。したがって、参照されているオブジェクトが、自分の
状態を変更するメソッドを持っている時には、副作用(それが主作用かも
しれませんが)に注意する必要があります。
Q2-7)メソッド名に定数は使えますか。
使えます。ただし、メソッド呼出しの時に引数を括る()を省略できません。
Q2-8)superがArgumentErrorになりますが
メソッド定義中でsuperと呼び出すと、引数がすべて渡されますので、
引数の数が合わないとArgumentErrorになります。異なる数の引数を
指定するには、super()に引数を指定してやります。
Q2-9)2段階上の同名のメソッドを呼びたいのですが、
superは、1段上の同名のメソッドを呼び出します。それより上の同名の
メソッドを呼び出すには、あらかじめそのメソッドをaliasしておきます。
Q2-10)組込み関数を再定義した時に、元の関数を呼びたい時はどうしますか
メソッド定義の中ではsuperが使えます。再定義する前にaliasしておくと、
元の定義が保たれます。Kernelの特異メソッドとしても呼べます。
Q2-11)破壊的メソッドとは何ですか
オブジェクトの内容を変更してしまうメソッドで、文字列や配列、ハッシュ
などにあります。同名のメソッドがあって、一方はオブジェクトのコピーを
作って返し、もう一方は変更されたオブジェクトを返すようになっている場合、
!のついた方が破壊的メソッドです。
Q2-12)*がついた引数は何ですか
引数が配列の時、要素を展開して渡します。a = [1,2] の時、k(a)では、
1個の配列がkの引数になりますが、k(*a)では、k(1,2)と同じになります。
メソッドの仮引数、caseのwhen節、多重代入の左辺や右辺でも使えます。
Q2-13)&がついた引数は何ですか
手続きオブジェクトをブロックとして受け渡しするための引数です。
引数列の一番最後に置きます。
Q2-14)"test"+a +"test"がparse errorになりますが
"test"+a(+"test")と解析されています。+の両側の空白をなくすか、いれるか
のどちらかにしてください。
Q2-15)p {}で何も表示されません
{}がハッシュのコンストラクタではなく、ブロックと解析されています。
p({})としてください。
Q2-16)s = "x"; puts s *10 がエラーになりますが
puts s *10 のところが、まずs *10をメソッド呼出しと解析されて
しまいます。s*10にするか、s * 10にしてください。
Q2-17)def pos= (val); print @pos,"\n"; @pos = val endと定義しても、
参照できません
=のついたメソッドは、レシーバー形式で呼ぶ必要があります。self.pos = a と
いう形で呼んでください。
3.文法(その他)
Q3-1)nilとfalseはどう違いますか
持っているメソッドの違いは、nil.methods - false.methods と
false.methods - nil.methods を表示してください。
ハッシュでは、nilを値として持つことができませんが、falseを持つことは
できます。
?のつくメソッドは、真偽を返す場合はtrue、falseを、そうでない時は
値もしくはnilを返します。
Q3-2)'\1'と'\\1'はどう違いますか
同じです。シングルクォートの中では、\'と\\だけが解釈され、それ
以外は解釈されません。
Q3-3)範囲オブジェクトのコンストラクタ..と...はどう違いますか
..は上端を含み、...は上端を含みません。
Q3-4)a?b:cというのは何ですか
if a then b else c endと同値です。
Q3-5)loopは制御構造ですか
メソッドです。ブロックは新しいローカル変数のスコープを導入します。
Q3-6)Marshalの使い方を教えてください
オブジェクトをファイルや文字列に格納しておき、後で再生できる
ようにするものです。格納は
Marshal.dump obj, io, lev
という形式で行います。ioには書き込み可能なIOオブジェクト、levは
オブジェクトが内容として他のオブジェクトを持っている時に、どこ
までオブジェクトの内容を格納するかを決めます。lev段までオブジェクト
をdumpしてもまだオブジェクトがある時には、そのオブジェクトのdumpは
オブジェクトの参照になりますので、再生した時には、参照が変わって
いるでしょうから、再生できないことになります。levのデフォルトは
100になっています。ioを省略した時には、文字列でdumpされます。
再生は
obj = Marshal.load io
または、
obj = Marshal.load str
という形式で、ioはdumpしたファイルを読み込み可能でopenしたもの、
strはdumpした文字列を指定します。
Q3-7)includeとextendはどう違いますか
include Fooは、クラス(モジュール)でFooの定数やメソッドを直接呼べるように
します。
obj.extend Fooは、Fooのメソッドをインスタンスobjの特異メソッドとして
追加します。
Q3-8)trapはどのように使いますか
trap("PIPE") {raise "SIGPIPE"}
Q3-9)instance_methods(nil)は何を返しますか
instance_methodsは、あるクラスのオブジェクトが持つメソッドを返しますが、
instance_methods(nil)は、スーパークラスから引き継いだものではなく、その
クラスで初めて定義されたメソッドを返します。
Q3-10)load と require、autoload の違いは何ですか
loadは、プログラムをロード、実行するという概念で、ファイルをパスから
探し、また、何度でも実行します。~をホームディレクトリに展開してくれます。
require、autoloadはライブラリをロード、実行するため、ライブラリのロード
パスから、.rb、.o(実際に探す拡張子は処理系依存)の拡張子のついたファイルを
一度だけロード、実行します。
Q3-11)tr("あ","a")がうまく動きません
組込みのtrは、バイトごとに変換します。require "jcode"とすると、
日本語を文字ごとに扱えます。
4.オブジェクト指向
Q4-1)クラス定義は、一度に行わなければなりませんか
同じクラスを再定義すると、前のクラス定義に追加されていきます。
メソッドを再定義した場合には後のものが上書きしますので、前のものは
失われます。
Q4-2)特異メソッドというのは何ですか
同じクラスに属するオブジェクトは、そのクラスで定義された属性や
メソッドを持っていますが、特定のオブジェクトだけに固有の属性や
メソッドを定義することができます。
特異メソッドを定義する方法としては、直接defで特異メソッドを
定義する方法、特異クラスを導入して、そこでメソッドを定義する方法、
モジュールでは、モジュール関数にする方法もあります。
Q4-3)特異クラスというのは何ですか
あるオブジェクトに固有の属性やメソッド(特異メソッド)を定義できるクラスです。
メタクラスとも呼ばれます。
Q4-4)クラスメソッドというのは何ですか
クラスの特異メソッドをクラスメソッドと呼びます。特異メソッドは
オブジェクトの固有のメソッドだと説明したばかりですが、rubyには、
メタクラスという概念があり、すべてのクラスは、同名のメタクラスと
いうものを持っていて、これは、Classクラスのインスタンスになって
います。ここにクラスメソッドが定義されます。
形式的にはクラス名をレシーバーとして呼べるメソッドということに
なります。
Q4-5)クラス変数はありますか
ありません。クラス定数にArrayやHashを使うことにより、クラス変数に
相当する動作をさせることができます。
Q4-6)クラスとモジュールの違いは何ですか
モジュールはインスタンスを作れません。
Q4-7)モジュール関数とは何ですか
privateメソッドであり、同時に特異メソッドでもあるものです。
特異メソッドとして使うときは、モジュール名.メソッド名という
形で使いますし、privateメソッドとして使うときは、
include モジュール名
メソッド名
という形で使います。
Q4-8)クラス定義の中でクラスメソッドを定義するのと、トップレベルでクラス
メソッドを定義する違いは何ですか
前者では定数を直接参照することができます。後者ではクラス名をつけて参照
しなければなりません。
5.プログラミング・チップス
Q5-1)ファイルを書き換えても変化しません
open("example", "w+").readlines.each_with_index{|l, i|
l[0,0] = (i+1).to_s + ": "} とやっても、exampleに行番号がつきません。
ファイルを書き換えているのではなく、readlinesで読み込んだ文字列を変えて
いるだけです。ファイルに書き戻してやらなければいけません。
io = open("example", "w+")
ary = io.readlines.each_with_index||l, i| l[0,0] = (i+1).to_s + ": "}
io.rewind
io.print ary
io.close
Q5-2)ファイルを時間の新しい順にソートしたいのですが
Dir.glob("*").filter{|f|[File.mtime(f), f]}.
sort{|a,b|b[0]<=>a[0]}.filter{|e|e[1]}
Q5-3)英語文字列の配列を辞書順にソートしたいのですが
ary.filter{|f|[f.downcase, f]}.sort.filter{|e|e[1]}
Q5-4)文字列から1文字ずつ取り出すにはどうしますか
split(//)やscan(/./)を使います。
Q5-5)"abcd"[0]は、何を返しますか
文字aのコード97(Fixnum)を返します。したがって、aかどうかを調べたい
ときは、?aと比較します。
Q5-6)シンボルから値を取り出すにはどうすればいいですか
eval((:symbol).id2name)とすると、値が取り出せます。
Q5-7)バックスラッシュをエスケープするにはどうしますか
Regexp.quote('\\')で、エスケープされます。gsubを使う場合には、
gsub(/\\/, '\\\\')では、置換文字列が構文解析で一度\\に変換され、
実際に置き換えるときにもう一度\と解釈されるので、gsub(/\\/,'\\\\\\')
とする必要があります。\&がマッチ文字列をあらわすことを使えば、
gsub(/\\/,'\&\&')と書けます。gsub(/\\/){'\\\\'}なら、エスケープが
1回しか解釈されませんので、求める結果が得られます。