プログラミング言語Ruby
プログラミング言語Rubyの勉強会資料です。次回Ruby on Rails(以下、RoR)の勉強会をするので、それにつながることに重点を置きます
プログラミング言語Ruby
- 2007/11/15
方針
- Rubyをまったく知らない前提で始めます
- Java、JavaScript、elispのようなメジャーな言語と対比しながら説明します
- なるべく手を動かして目に見える形で説明を進めます
- 次回Ruby on Railsの勉強会をするので、それにつながることに重点を置きます
- 逆に言うと、RoRであまり使わない知識は流します。例えば、ファイル操作周りやスレッド周りなど
Rubyのバージョン
$ ruby --version ruby 1.8.5 (2006-08-25) [i486-linux]
開発環境(2)
.emacs.elに書くべき記述
; ruby (autoload 'ruby-mode "ruby-mode") (setq auto-mode-alist (cons '("\\.rb$" . ruby-mode) auto-mode-alist)) (setq interpreter-mode-alist (append '(("ruby" . ruby-mode)) interpreter-mode-alist)) (autoload 'run-ruby "inf-ruby" "Run an inferior Ruby process") (autoload 'inf-ruby-keys "inf-ruby" "Set local key defs for inf-ruby in ruby-mode") (add-hook 'ruby-mode-hook '(lambda () (inf-ruby-keys))) (autoload 'rubydb "rubydb3x" "run rubydb on program file in buffer *gud-file*. the directory containing file becomes the initial working directory and source-file directory for your debugger." t)
表記
1 + 1 #=>2 (返り値)
> 1 + 1 2 (返り値)
> print "foo\n" foo (出力)
> foobar NameError: undefined local variable... (エラー出力)
いきなりですが、Ruby初見の個人的な印象
- 概念モデルはキレイですが、構文は結構複雑です
- 驚き最小の法則をうたっていますが、予約語っぽく見えた語がメソッドで、意外に驚きます
- 記述の省略の多さに最初は違和感がありましたが、慣れました
- to_s というメソッド名を良しとしてしまう文化は、Unix文化圏だと感じます
宣言や予約語に見える語の多くがメソッド(1)
後で驚かないために、最初に書いておきます
- public/protected/private
- attr_accessor/attr_reader/attr_writer
- new
- include/exclude
- load/require
- catch/throw
- loop
宣言や予約語に見える語の多くがメソッド(2)
メソッド呼び出し時にカッコを省略できるので、最初、メソッド呼び出しに見えません。 しかし、メソッド呼び出しだと思って見ていると、だんだんメソッド呼び出しに見えてきます。
class MyClass attr_accessor :my_prop public :my_method end
- attr_accessorというメソッドに引数 :my_prop を渡している
- publicというメソッドに引数 :my_method を渡している
- :foo はシンボルです(後述)
演算子に見える語も結構メソッド
- 数値演算 1 + 2 は + というメソッドに引数 2 を渡している
- 数値比較演算 1 < 2 は < というメソッドに引数 2 を渡している
- 配列要素の参照 array[0] は [] というメソッドに引数 0 を渡している
- 配列要素への代入 array[0] = 1 は []= というメソッドに引数 0 と 1 を渡している
オブジェクトのプロパティアクセスに見えるのもメソッド
- オブジェクトのプロパティアクセス obj.prop は、propというメソッドを呼んでいる
- オブジェクトのプロパティアクセス obj.prop = 0 は、prop=というメソッドに引数 0 を渡している
詳細は後述
メソッドに見えないメソッド呼び出しの具体例
RoRのコード例から引用
class MyController < ApplicationController def index if params['name'] == 'unknown' render :action => 'x' else render :action => 'y' end end end
- params[]というメソッドに引数 'name' を渡している ...間違い(参照 http://dev.ariel-networks.com/Members/inoue/ruby-article2/)
- その戻り値の == というメソッドに引数 'unknown' を渡している
- renderというメソッドに引数 :action=>'x' を渡している
- renderというメソッドに引数 :action=>'y' を渡している
- :action=>'x' は囲む {} が省略されています(後述)
Rubyには関数や手続きは存在しません
すべてメソッドです。
組込み関数に見えるもの(例えばprint)は、たいていObjectクラスの(クラス)メソッドです。正確にはKernelモジュールのメソッドで、Objectクラスにmix-inされています(後述)。
hello worldと初歩的な構文(2)
- 文終端の ;(セミコロン) は不要(あっても良い)
- 文法的には ';' or '\n' が文の終端
- しかし、以下は有効なので、微妙に気持ち悪い
1 + 2 #=>3
- 以下は1の後ろの改行で文が終端します
1 + 2
hello worldと初歩的な構文(3)
- メソッド呼び出しのカッコは省略できます。
print("hello world\n")
と書いてもOKです。
- Ruby文化は結構カッコを省略します。最初は気持ち悪かったですが、慣れました。
コメントの構文
# で始まる行 =begin この中 =end
=begin この中 =begin この中 =end
は有効のようです(しかし、emacsのruby-modeは狂うのでお薦めしません)
なんでもオブジェクト(重要)
- 実体のあるものは、すべてオブジェクト
- 変数はJavaで言えば、すべて参照型 (概念モデル上、値型の変数は存在しません)
構文などは複雑ですが、この概念モデルは一貫しています。
クラスベースとプロトタイプベース
- 基本的にはJava同様、普通のクラスベースのオブジェクト指向です
- すべてのオブジェクトはクラスに属します
- クラスは階層構造をなします (継承関係を作ります)
- クラス階層は単一階層です(トップがObjectクラス)
- JavaScriptのような、プロトタイプベースのオブジェクト指向もできます(後述)
オブジェクトを作る方法
Stringオブジェクトを作る方法の数々
- String.new("foo") # 後述
- String("foo") # 実体はKernelモジュールのファクトリ的なメソッド
- "foo" # リテラル表現
- その他、色々とStringを返すメソッド。例えば 1.to_s
オブジェクトは本質的に「名無し」であることに注意。
オブジェクトを調べるメソッド
- obj.class
- obj.object_id
- obj.methods
- obj.public_methods
- obj.protected_methods
- obj.private_methods
- obj.class.superclass
- obj.class.instance_methods
- obj.class.included_modules
以下、代表的なクラスのオブジェクトを片っ端から調べます
リテラルでオブジェクトを作ってそのクラスを調べる
1.class #=>Fixnum (0.1).class #=>Float (1<<32).class #=>Bignum (?a).class #=>Fixnum (カッコは不要) "foo".class #=>String [].class #=>Array {}.class #=>Hash /foo/.class #=>Regexp (0..1).class #=>Range Object.class #=>Class :if.class #=>Symbol
色々あって複雑ですが、ここに挙がったリテラル表現とクラスが分かると、Rubyのコードはだいたい読めます。
逆に、上記の理解が無いと、Rubyのコードはかなり意味不明になります。
数値(1)
1.methods #=> 色々 (+,-,*,/,% あたりがあることに注目) 1.class.instance_methods
クラス階層はこんな感じ
1.class #=> Fixnum 1.class.superclass #=>Integer 1.class.superclass.superclass #=>Numeric 1.class.superclass.superclass.superclass #=>Object
数値(2)
- FixnumとBignumがあります。
- 勝手に変換されます (なので気にしなくてOK)
# どうでもいい境界 (1<<29).class #=>Fixnum (1<<29 + 1).class #=>Bignum
数値(5)
(0.1).class #=>Float (0.1 * 10).class #=>Float (10 * 0.1).class #=>Float (0.1 + 0.9).class #=>Float
整数にしたい場合、慌てず騒がず (0.1).methods を見て当たりをつけて、
(0.1).to_i #=>0 (1.1).to_i #=>1
文字列(1)
"s".methods #うんざりするほどたくさんのメソッド(でも、気にしない) "s".class.instance_methods
クラス階層はこんな感じ
"s".class #=>String "s".class.superclass #=>Object
文字列(2)
基本的なメソッドの例(使っていれば嫌でも覚えます。使わないと嫌になるほど忘れます)
"abc".length #=>3 "abc".size #=>3 "abc".index("b") #=>1 "abc".index("z") #=>nil
lengthとsizeメソッドは別名(同じ動作)
文字列(3)
文字列の内部に [] でアクセスできるのはCみたいです...
"abc"[0] #=>97 "abc"[1] #=>98 "abc"[0,0] #=>"" "abc"[0,1] #=>"a" "abc"[0,10] #=>"abc" ...範囲を越えた指定は単に無視 "abcdef"[1,3] #=>"bcd" "abcdef"[0..3] #=>"abcd" "abcdef"[0...3] #=>"abc"
部分文字列を取得するには indexとlength を渡す必要があります。 もしくは first..last, first...last の形(後述するRange)を渡す必要があります。
負の数も渡せます(詳細はヘルプを参照)。
文字列(5)
"abc" + "def" #=>"abcdef" "abc" << "def" #=>"abcdef"
同じ値を返しますが、動作は異なります。
前者は非破壊的(連接した新しい文字列オブジェクトを返す)、後者は破壊的です("abc"文字列オブジェクトの後ろにそのまま連接)。
文字列(6)
- [重要] 文字列は変更可能(mutable) (Java、JavaScript、Pythonと異なります)
- しかも、破壊的なメソッドが意外に多いです(後述する配列とハッシュテーブルも同様)
変数
この辺で、変数が無いと説明が辛いので、唐突ながら変数を使いはじめます(後でもう少し詳しく)。
s = "abc" #=>"abc" s #=>"abc"
「変数sが文字列オブジェクト"abc"を参照している」と読んでください
見て分かるように、
- 変数は宣言なし (varもmyもletもなし。初期化すれば使えます)
- 変数は型なし
文字列(7)
破壊的な操作の意味
s = "abc" s.object_id #=>"abc"オブジェクトを(実行環境で)一意に識別する数値 (s + "def").object_id #=>異なる値 s #=>"abc" ...sの参照するオブジェクトは変化無し (s << "def").object_id #=>"abc"の時と変わらず s #=>"abcdef" ...sの参照するオブジェクトは変化した
文字列(8)
破壊的な操作の意味パート2
s1 = "abc" s2 = s1 # 変数s2とs1は同じオブジェクトへの参照を持つ s1 << "def" s1 #=>"abcdef" #変数s1の参照するオブジェクトを変更 s2 #=>"abcdef" #変数s2から見ても変更されている
[]=、insert、replace、reverse! など、破壊し放題
破壊的メソッドの命名
- 破壊的メソッドには reverse! などのように ! をつける規約があります
- ただし、すべてに付くわけではありません
- 例えば、文字列の []= は ! のつかない典型的な破壊的メソッドです
s = "abc" s[0] = ?z #=>"zbc" s[1] = "GNU" #=>"zGNUc"
cf. ? のつくメソッド(述語メソッド)
破壊させたくない場合
s = "abc" s.freeze s[0] = "A" TypeError: can't modify frozen string
freezeはObjectクラスのメソッドなので、すべてに通用します。
cf. JavaのunmodifiableCollection()メソッド
数値オブジェクトの破壊的メソッド? (1)
i1 = 0 i2 = i1 i1 #...ここで変数i1の参照するオブジェクトに破壊的操作を加えて、値が 1 になったとする
変数i2の返す値は 1 になるのでしょうか? (解答は次ページへ続く)
数値オブジェクトの破壊的メソッド? (2)
前ページからの続き。
概念モデル上は 1 になるべきです。
しかし、実際は「数値オブジェクトには破壊的メソッドが存在しない」ため、こういうことが起きることはありません(概念モデルの一貫性は保たれています)。
文字列(10)
'' の文字列は、""の文字列より、エスケープ処理が少ない。次のふたつのエスケープのみです。式展開もありません。
\\ \'
'foo\n'.length #=>5 'foo\\'.length #=>4 'foo\''.length #=>4
文字列[参考](11)
区切り文字(|)は任意
%Q|foo|.class #=>String "foo" %q|foo|.class #=>String 'foo' %x|ls|.class #=>String `ls` %w|1 2|.class #=>Array [1,2] %r|foo|.class #=>Regexp /foo/
数値と文字列の変換(1)
1.to_s #=>"1" "1".to_i #=>1
実に直感的ですが、Javaならこんなメソッド名は許さないでしょう。
引数で基数を指定できます
8.to_s(2) #=>"1000" 8.to_s(8) #=>"10" "1000".to_i(2) #=>8 "10".to_i(8) #=>8
数値と文字列の変換(2)
意外に暗黙の型変換はしてくれません。
print "foo" + 10
はエラー(TypeError: can't convert Fixnum into String)
print "foo" + 10.to_s
にします
配列(1)
配列のリテラル表現は [] です。
[].methods #Stringと共通点が多い(Enumerableモジュールをmix-inしているから) [].class.instance_methods
クラス階層はこんな感じ
[].class #=>Array [].class.superclass #=>Object
配列(2)
リテラル表現の続き
arr1 = [0,1,2] arr2 = ["foo", "bar", "baz"] arr3 = ["foo", 0, "baz"] arr4 = [arr1, arr2] #=>[[0, 1, 2], ["foo", "bar", "baz"]]
気にせず何でも放りこめます(Javaなら驚くかもしれませんが、JavaScript、elisp、Perl辺りから見ればあまり驚かないでしょう)
配列(3)
Stringクラスから推測できるような操作
arr = ["foo", "bar"] arr << "baz" #=>["foo", "bar", "baz"] arr.length #=>3 arr[0] #=>"foo" arr[1,2] #=>["bar", "baz"] arr[0] = "FOO" #=>["FOO", "bar", "baz"]
破壊的メソッドにピンと来ない人は、文字列に戻って復習してください。
配列(4)
# valuesをarrayに(特に可変長引数のメソッド引数で使う) *arr = 0,1,2,3 #=>[0, 1, 2, 3] v1,v2 = *a #...arrayを展開(不要?) v1,v2 = a #...これでも動く
# 可変長引数のメソッドは次のように書きます def method(*arg) arg[0] # 0番目の引数 end
return 0,1,2,3 #...メソッドが配列を返す
配列(5)
Perlっぽい(?)操作
arr = [0,1,2,3,4] arr.push("a") #=>[0, 1, 2, 3, 4, "a"] arr.pop #=>"a" arr #=>[0, 1, 2, 3, 4] arr.unshift("a") #=>["a", 0, 1, 2, 3, 4] arr.shift #=>"a"
- Arrayは内部的にリストではなく配列なので、unshiftはpushより遅い
- これらはすべて破壊的メソッド
配列(6)
集合演算
a = [1,5,2,3,9,1] b = [7,9,3] a - b #=>[1, 5, 2, 1] ...subtract a & b #=>[3, 9] ...intersect a | b #=>[1, 5, 2, 3, 9, 7] ...union
配列(7)
配列の要素をなめる処理 (詳細はブロックの説明の後)
arr = ["foo", "bar", "baz"] #パターン1 arr.each { |e| print e + "\n" } #パターン2 for e in arr print e + "\n" end #パターン3 i = 0 while i < arr.length print arr[i].to_s + "\n" i = i + 1 end #パターン4 arr.length.times { |i| print arr[i].to_s + "\n" }
ハッシュテーブル(1)
ハッシュテーブルのリテラル表現は {} です。
{}.methods #Stringと共通点が多い(Enumerableモジュールをmix-inしているから) {}.class.instance_methods
クラス階層はこんな感じ
{}.class #=>Hash {}.class.superclass #=>Object
ハッシュテーブル(2)
リテラル表現の続き
ht1 = { "key1" => "val1", "key2" => "val2" } ht2 = { "key1" => "val1", "key2" => 0 } ht3 = { "key1" => ht1, 0 => ht2 }
キーもバリューも何でも放りこめますが、今さら、驚くことはないでしょう。
ハッシュテーブル(3)
ハッシュテーブルのキーには何でも使えますが、シンボル(後述)を使うことが多いです
# こうするより ht1 = { "key1" => "val1", "key2" => "val2" } # こう書く ht1 = { :key1 => "val1", :key2 => "val2" }
使い勝手と可読性は文字列と同程度で、実行が速いので、使えるならシンボルを使うこと。
ハッシュテーブル(4)
ハッシュテーブルの要素の参照
ht1 = { :key1 => "val1", :key2 => "val2" } # Perlの癖で、ht1{:key1} と書きたくなりますが # 次のように書くのが正解です ht1[:key1] #=>"val1" # 要素の追加は次のように書きます ht1[:newkey] = "newval"
ハッシュテーブル(5)
JavaScriptの感覚で、もしかして配列とハッシュテーブルは同じ?、と思うかもしれませんが、違うものです
arr = [] arr[:key] = 0 TypeError: Symbol as array index
ハッシュテーブル(6)
ハッシュテーブルの要素をなめる処理
ht = { :k1 => 1, :k2 => 2, :k3 => 3 } ht.each { |k,v| print "key is " + k.to_s + ", value is " + v.to_s + "\n" } ht.each_key { |k| print "key is " + k.to_s + ", value is " + ht[k].to_s + "\n" }
詳しくはブロックの説明の後。
ハッシュテーブル(7)
メソッドの最後の引数がハッシュテーブルの時、{} を省略できます。
# 一番省略しないパターン method({:key1 => "val1", :key2 => "val2"}) # 一番省略したパターン (こっちがRubyでは一般的) method :key1 => "val1", :key2 => "val2"
- 名前つきパラメータっぽい動きができます (RubyのMLによると、Ruby2.0でkeyword引数を言語仕様として持って、このスタイルをobsoleteにする話もあります)
正規表現(1)
正規表現のリテラル表現は // です。
//.methods //.class.instance_methods
クラス階層はこんな感じ
//.class #=>Regexp //.class.superclass #=>Object
正規表現(2)
正規表現のリテラル表記を言語仕様上持つのは awk, Perl を継ぐ正統Unix文化を感じます。
/foobar/ がRegexpクラスのオブジェクトになるのは、感覚的にはJavaScriptに近いです。
Regexp.new("foo") /foo/
正規表現(3)
/foo/ =~ "foo" #...Regexpクラスの =~ メソッド "foo" =~ /foo/ #...Stringクラスの =~ メソッド
Ruby本では前者を推奨しています
- マッチ文字列は $&
- グループ化マッチは $1 など
- マッチ情報全体は $~
正規表現(4)
m = /foobar/.match('foobar') m.class #=>MatchData m[0] #...マッチ全体 m[1] #...グループ化マッチ1
Range(2)
動作確認
(0..3).each { |n| print n.to_s + ", " } 0, 1, 2, 3, #出力 #=>0..3 #eachの返り値 (0...3).each { |n| print n.to_s + ", " } 0, 1, 2, #出力 #=>0...3 #eachの返り値
Range(3)
Rangeの用途
- case文の比較用の値
- Enumerableの [] or []= に渡す引数
- for文
case num when 0..10 print "num is between 0 and 10" end "abcdef"[0..2] #=>"ab" for i in 0..10 print i.to_s + ", " end #=> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0..10
シンボル(1)
シンボルとは、「文字列から(実行環境で)一意に決まる数値」です。
使う視点で見れば、文字列の可読性と数値の速度の両方を兼ね備えたモノです。
"foo".intern #=>:foo "foo".intern.class #=>Symbol
シンボル(2)
シンボルのリテラル表現は :foo です。
:foo.methods :foo.class.instance_methods
クラス階層はこんな感じ
:foo.class #=>Symbol :foo.class.superclass #=>Object
シンボル(3)
"foobar".intern #...文字列からシンボル取得 :"foobar" #...文字列からシンボル取得(コンパイル時) :foobar #...識別子からシンボル取得(コンパイル時)
:foobar.to_s #...シンボルから文字列 :foobar.to_i #...シンボルから数字
シンボル[余談](5)
少しだけ実装を見てみると(シンボルとは何かが一発で分かるので)
#define ID_SCOPE_SHIFT 3 // 新しいシンボルを得るたびに、こんな処理 id |= ++last_id << ID_SCOPE_SHIFT;
結局、下位3ビットに種別を示す値。上位ビットは単にincrementしていく整数。
取得したシンボルは、両方向から取得できるように2つのハッシュテーブルへ格納。 (一度、数値を割り当てた文字列であれば、2度目以降は同じ数値を返す。逆も然り)
シンボル[余談](6)
全シンボルの一覧
# cf. elispのobarray Symbol.all_symbols
cf. 全オブジェクト一覧
ObjectSpace.each_object() { |m| p m } #...すべて ObjectSpace.each_object(String) { |m| p m } #...Stringオブジェクトすべて
シンボル[余談](7)
シンボルが既に存在するかをチェックするために
Symbol.all_symbols.include? :unknown #=>true (常に) # 引数を評価した時点で intern されてしまう...
シンボル生成とinternは分離できない? とりあえず文字列で比較すると次のようになります。
Symbol.all_symbols.map { |e| e.to_s }.include? "unknown"
変数ふたたび(1)
- $foo ...$で始まる名前はグローバル変数
- @foo ...@で始まる名前は(クラスの)インスタンス変数
- @@foo ...@@で始まる名前はクラス変数
- Foo ...大文字で始まる名前は定数
- foo ...その他はローカル変数
変数ふたたび(2)
- 初期化すれば使えます
- すべて(いわゆる)参照型なので、いくらでも再利用可能です
x = 1 x = "abc" x = MyClass.new
言語上、エラーにはなりませんが、このような再利用はバグの元です
変数ふたたび(3)
初期化していない変数を参照すると次のエラーがでます。
> unused NameError: undefined local variable or method `unused' for main: > Unused NameError: uninitialized constant Unused
しかし
> @unused nil > @@unused NameError: uninitialized class variable @@unused in Object
そもそも、クラス定義でも無い場所(トップレベル)でのインスタンス変数の意味は? (後述)
定数
- 大文字で始める名前
- 参照オブジェクトが変更不能になるわけではない(cf. freezeメソッド) => 再代入がwarningになるだけ
> Foo = "foo" > Foo = "bar" warning: already initialized constant Foo > Foo #=> "bar" ...警告が出ますが、参照オブジェクトは変えられる
- cf. elispのdefconst
クラスを理解する上で重要なクラス/モジュール
- Object ...全てのクラスのスーパークラス
- Kernel ...Objectにmix-inされるモジュール
- Module ...モジュールのクラス
- Class ...クラスのクラス
クラス(2)
次のようにメソッドを定義できます。
class MyClass def mymethod print "...my method\n" end end
先ほど作ったオブジェクトでメソッドを呼んでみると
obj.mymethod ...my method
メソッドの返り値は、最後の評価結果もしくは return foo
クラス(3)
インスタンス変数は @foo の形
class MyClass def init @val = 51 end def output print "..." + @val.to_s + "\n" end end obj.output #=>nil ... #@valに値が無いので改行のみ obj.init #=>51 obj.output #=>nil ...51
クラス(4)
- コンストラクタ
- オブジェクトは クラス名.new の形で生成します
- この new は演算子ではなく Object クラスのメソッドです。
- 理屈上、MyClassで newメソッドの定義を書き換えることもできますが、普通はしません。
Objectのnewメソッドは initializeメソッドを呼ぶ実装になっているので、MyClassでinitializeメソッドを実装します(template method pattern)。
クラス(5)
- コンストラクタ(続き)
class MyClass def initialize print "...init\n" end end obj = MyClass.new ...init
- 普通は initializeメソッドの中で、インスタンス変数の初期化を行います
クラス(6)
インスタンス変数は(Java風に言えば)常に privateスコープ です。
> obj = MyClass.new > obj.@val SyntaxError: compile error > obj.val NoMethodError: undefined method `val' for #<MyClass:0xb7c79ab8>
Javaファンが大好きな getter, setter の出番か?
クラス(7)
インスタンス変数valにアクセスするために、get_val(or getVal)やset_valというメソッドを作ってもありですが、Rubyでは次の名前のアクセッサメソッドを定義するのが慣例です。
class MyClass def initialize @val = 51 end def val @val end def val=(val) @val = val end end
Javaのように美しい...
クラス(8)
たくさんコードを書くのが面倒なので、次のように書けます(attr_accessorはModuleクラスのメソッド)
class MyClass attr_accessor :val end
これで次のように書けます。
obj = MyClass.new obj.val = 51 obj.val #=> 51
クラス(9)
attr_accessorで、内部的には手で書いた場合と同じメソッドができているだけです
MyClass.instance_methods # valとval= メソッドがある
他に
- attr_reader
- attr_writer
もあります(意味は調べるか、推測してください)
クラス(10)
クラス定義は書けばどんどん(既存定義に)追加可能です
class MyClass def doit print "...doit\n" end end
こう書くと、以前のMyClassを破棄して新たなMyClass定義を行うのではなく、元からあるMyClassにdoitメソッドを追加する動作になります。
クラス(11)
標準クラスも変更し放題
class String def my_method print "...my method\n" end end "abc".my_method ...my method
クラス(13)
継承
なんとなく想像がつくかもしれませんが、MyClassはObjectクラスを継承しています。
MyClass.superclass #=>Object
一般にクラスの継承は次のように書きます(Objectからの継承の場合だけ、省略可能)
class MyClass < Object end
クラス(14)
継承(続き)
class MyString < String def doit print "...doit\n" end end mys = MyString.new("abc") mys << "def" #... << はStringクラスのメソッドです mys #=>"abcdef" ...Stringクラスのオブジェクトとして普通に使えて mys.doit # かつ、doitメソッドも持っている ...doit
クラス(15)
- Rubyには多重継承はありません(Javaと同じ)
- Moduleでmix-inするのがRuby流です
モジュール(1)
モジュールは次のように定義します(ほぼクラス定義と同じ)。
module MyModule def mymethod print "...my method in module\n" end end
モジュール(2)
モジュールはインスタンス化できません。
モジュールを使うには、クラスからincludeするか、オブジェクトをextendします(後述)。
class MyClass2 include MyModule end obj2 = MyClass2.new obj2.mymethod ...my method in module
モジュール(3)
モジュールは、多重継承の代償として使える、インスタンス化できない、という特徴から、一見Javaのinterfaceと似ていますが、
- (Javaの)interfaceは型(振る舞い)を強制する仕組み
- Rubyのモジュールは、実装の共有のための仕組み
という違いがあります。
しかし、結果論として、モジュールでinterface相当のことはできてしまいます(Enumerableは典型的)
モジュール[参考](4)
Array.included_modules #=>[Enumerable, Kernel]
template method pattern(1)
class Base def doit print love end end class Derived < Base def love "...i love you" end end obj = Derived.new obj.doit ...i love you
template method pattern(2)
モジュールでも可能
module Base def doit print love end end class Mix include Base def love "...i love you" end end obj = Mix.new obj.doit ...i love you
特異メソッド、特異クラス定義(1)
JavaScriptファンにはお馴染みですが、特定のオブジェクトだけにメソッド定義をできます。
s = "abc" # 変数sが参照するオブジェクトのクラスはString def s.doit print "...doit\n" end s.doit ...doit
特異メソッド、特異クラス定義(2)
他の書き方
s = "abc" def << s def doit print "...doit\n" end end def s::doit print "...doit\n" end
オブジェクトをextend
モジュールの機能を特定のオブジェクトでだけ使えるようにする
s = "abc" s.extend MyModule s.mymethod ...my method in module
クラスメソッド(1)
- クラスメソッドの書き方 (3種類)
class MyClass def self.mymeth #...推奨? print "...my meth\n" end def MyClass.mymeth print "...my meth\n" end class << self def mymeth print "...my meth\n" end end end
クラスメソッド(2)
- クラスメソッドの呼び方 (2種類)
- MyClass.meth
- MyClass::meth ...推奨?
のどちらでも呼べます
クラスメソッドという言語仕様はなく、単にクラスオブジェクトの特異メソッドです(後述)
クラスもオブジェクト(1)
MyClass.class #=>Class MyClass.class.superclass #=>Module MyClass.class.superclass.superclass #=>Object
MyClassはClassという名前のクラスのオブジェクトらしい。と言うことは...
クラスもオブジェクト(3)
MyClassの定義は次のようにも書けます。
MyClass = Class.new() # 普通のメソッドの定義 MyClass.send(:define_method, :my_method, Proc.new {print "my method"\n}) # クラスメソッドの定義 def MyClass.meth ... end
クラスもオブジェクト(4)
大文字で始まる変数は定数、というルールから
- class MyClass ... end のやっていることは、Classクラスのオブジェクトを作って、MyClassという定数の変数に代入している
実行フロー(1)
Javaなどの感覚では、クラス定義の中は実行フローと違う感覚を持ちますが、
class MyClass この中は特殊な印象 (定義部分) end
Rubyの場合、クラス定義の中も上から下に実行される実行フローです。
- クラスメソッドの定義は、クラス定義の中に書こうとと外に書こうと同じ
実行フロー(2)
#!/usr/bin/ruby print "1 " + self.class.to_s + ", " + self.to_s + "," + self.object_id.to_s + "\n" class MyClass print "2 " + self.class.to_s + ", " + self.to_s + "," + self.object_id.to_s + "\n" def initialize print "4 " + self.class.to_s + ", " + self.to_s + "," + self.object_id.to_s + "\n" end def doit print "5 " + self.class.to_s + ", " + self.to_s + "," + self.object_id.to_s + "\n" end end print "3 " + self.class.to_s + ", " + self.to_s + "," + self.object_id.to_s + "\n" obj = MyClass.new obj.doit print "6 " + self.class.to_s + ", " + self.to_s + "," + self.object_id.to_s + "\n"
実行フロー(3)
実行結果
1 Object, main,-605690628 2 Class, MyClass,-605725298 3 Object, main,-605690628 4 MyClass, #<MyClass:0xb7cab860>,-605725648 5 MyClass, #<MyClass:0xb7cab860>,-605725648 6 Object, main,-605690628
- 特に 2 に注目。selfがClassクラスのオブジェクト(MyClass自身)を参照
- 4や5では、selfがMyClassクラスのオブジェクト(トップレベルでobj変数が参照しているオブジェクト)を参照
トップレベル(2)
Object.instance_methods #=> top_doitがある self.class #=>Object self.to_s #=>"main"
@foobar = 10 self.instance_variables #=> @foobar
ブロック(1)
既に出ていますが、次の {} で囲まれた部分がブロックです(eachはメソッドです)。
arr = [0,1,2,3] arr.each { |e| print e.to_s + "\n" }
非常に簡単に言えば、「(構文上メソッドの引数に見えないが)メソッドの引数として渡るクロージャ」です。
ブロック(2)
arr = [0,1,2,3] arr.each { |e| print e.to_s + "\n" }
に近いコードを、prototype.js(JavaScript)で書けば、
arr.each(function(e){ alert(e); })
正直、JavaScriptの方が構文と概念の一貫性がある気がします。
ブロック(3)
一見、メソッドに関数ポインタらしきものが渡っている?、と思うかもしれませんが、
num = 51 arr.each { |e| print((e+num).to_s + "\n") }
ブロックから見て外側のローカル変数を参照できる点が異なります(ローカル変数のスコープを抜けても、ブロックから参照できます)。
ブロック(5)
ブロックを渡される側のメソッドの書き方:パターン1
def my_loop(arr) for e in arr yield e end end
my_loop([0,1,2,3]) { |e| print((e * 2).to_s + ",") } 0,2,4,6,
ブロック[参考](7)
def my_while(cond) return unless cond yield retry end
i = 0 my_while (i<10) do print i.to_s + "\n" i += 1 end
ブロック[参考](8)
結局、Procクラスを明示的に扱う方がわかりやすいかも(でも、あまり見ないコードなのでやめた方が良い)
def ret_closure local_val = 51 p = Proc.new { |i| print((i + local_val).to_s) } # p = lambda { |i| print((i + local_val).to_s) } #こうも書けます return p end
p = ret_closure p.call(9) #=>60を表示 (9 + 51)
例外(1)
class MyExcep < Exception end def doit raise MyExcep, "message" end begin doit rescue MyExcep print $!.to_s else print "no exception" ensure print "finally" end
例外(2)
- rescue節の中で例外オブジェクトの参照 => $!
- $@ がコールスタック(Array)
比較
- equal? ...同一オブジェクト(object_idが一致)
- == ...値の比較
- === ...caseで使われる
- eql? ...hash tableのキーの比較に使われる
- <=> ...ソートで使われる
misc.テクニック
メソッドが存在しない時に呼ばれるメソッド
method_missing(method_id, *arguments)
RoRでfind_by_name_and_password のようなメソッドを実現
Rubyの慣習
- symbolを多用
- 複数行のブロックは do end。単一行は { }
- コマンドや宣言っぽいメソッド呼び出しでは () を省略
- メソッド呼び出しの最後の引数のハッシュは {} を省略
- requireは一度の読み込み(rbとlibrary)。loadは複数回の読み込み
- loadは主に設定用
注意
- ++ 演算はない
- 0が偽ではない (falseとnilは偽)