Personal tools
You are here: Home 原稿・資料 ワークス、アリエル共同勉強会 プログラミング言語Ruby
Document Actions

プログラミング言語Ruby

プログラミング言語Rubyの勉強会資料です。次回Ruby on Rails(以下、RoR)の勉強会をするので、それにつながることに重点を置きます

方針

  • Rubyをまったく知らない前提で始めます
  • Java、JavaScript、elispのようなメジャーな言語と対比しながら説明します
  • なるべく手を動かして目に見える形で説明を進めます
  • 次回Ruby on Railsの勉強会をするので、それにつながることに重点を置きます
  • 逆に言うと、RoRであまり使わない知識は流します。例えば、ファイル操作周りやスレッド周りなど

Rubyのバージョン

$ ruby --version
ruby 1.8.5 (2006-08-25) [i486-linux]

開発環境(1)

Debianでインストールした方が良いパッケージ

  • ruby
  • ruby-elisp
  • rake
  • rdoc

開発環境(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)

開発環境(3)

何はともあれ

M-x run-ruby

小指を怪我した場合

$ irb

開発環境[参考](4)

デバッグするには

M-x rubydb

表記

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と初歩的な構文(1)

ようやく hello world

#!/usr/bin/ruby
print "hello world\n"

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

数値(3)

  • 文字は ?a のように書きます (elispと同じ)
?a        #=>97
?a == 97  #=>true

数値(4)

参考までに

  • 0b00001 ...2進数
  • 010 ...8進数
  • 0x10 ...16進数

数値(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)を渡す必要があります。

負の数も渡せます(詳細はヘルプを参照)。

文字列(4)

[] はメソッドなので、次のようにも書けます。

"abc".[](0,1)  #=>"a"

後述しますが、[]= というメソッドもあります。

文字列(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 になるべきです。

しかし、実際は「数値オブジェクトには破壊的メソッドが存在しない」ため、こういうことが起きることはありません(概念モデルの一貫性は保たれています)。

文字列(9)

式展開。結構便利です。

i = 51
"abc#{i}xyz"        #=> abc51xyz
"abc#{i * 2}xyz"    #=> abc102xyz

文字列(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(1)

Rangeのリテラル表現は次の2種です

n..m    #nからmまで(mを含む)
n...m   #nからmまで(mを含まない)

クラス階層は省略

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      #...シンボルから数字

シンボル[余談](4)

やってみると

"sym1".intern.to_i
"sym2".intern.to_i
"sym3".intern.to_i

8ずつ増えるように見えます。

シンボル[余談](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 ...クラスのクラス

クラス(1)

class MyClass
end

でクラス定義をして、次のようにオブジェクトを作ります。

obj = MyClass.new
obj.class   #=>MyClass

クラス(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

クラス(12)

undef でメソッドを無効にできます

class String
  undef my_method
end

クラス(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流です

クラス(16)

可視性

  • public
  • protected
  • private
  • 自分で調べてください
  • 残念ながら意味がJavaと少し違います

モジュール(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という名前のクラスのオブジェクトらしい。と言うことは...

クラスもオブジェクト(2)

オブジェクトは クラス名.new で作れるのだから、

MyClass = Class.new

でも良いはず。

クラスもオブジェクト(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変数が参照しているオブジェクト)を参照

トップレベル(1)

普通に書く

def top_doit
  print "top doit\n"
end

とは何か?

トップレベル(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") }

ブロックから見て外側のローカル変数を参照できる点が異なります(ローカル変数のスコープを抜けても、ブロックから参照できます)。

ブロック(4)

ブロックはdo...endでも書けます。

arr.each do |e| 
  print e.to_s + "\n"
end

ブロック(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,

ブロック(6)

ブロックを渡される側のメソッドの書き方:パターン2

def my_loop(arr, &block)
  for e in arr
    block.call e
  end
end

ブロック[参考](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)

ifとかelseとか...

自分で調べてください。

  • break
  • next ...continue相当
  • redo ...continue相当 without 評価

制御構文(2)

文が値を持つので

x = a ? b : c

x = if a then b else c end

と書けます(さよなら、3項演算子)

例外(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は偽)

To be continued

次回は Ruby on Rails

来月 or 来年?


Copyright(C) 2001 - 2006 Ariel Networks, Inc. All rights reserved.