ようへいの日々精進XP

よかろうもん

俺は Ruby について 1bit も理解していなかったので写経する 〜 組み込みクラス編 〜

これは

  • Ruby Gold を受験するにあたって学んでいること等を写経したり, メモったりしています
  • Ruby 技術者認定試験合格教本」を主に参考にしており, 今回は第五章「組み込みクラス」を写経していきます
  • 実行例では主に pry を利用していますが, 例外が発生した際の実行過程出力(from から始まる行)は省略しています...すいません
[12] pry(main)> Baz2.new.private_method1
NoMethodError: private method `private_method1' called for #<Baz2:0x005606f46e2778>
from (pry):29:in `__pry__'

Module クラス

Module クラスとは

  • ある機能をひとまとめにしたモジュールの為のクラス
  • クラスである Class クラスは, Module クラスを継承している為, 全てのクラスでこらの有用なメソッドが利用出来る
[15] pry(main)> module MyMethods
[15] pry(main)*   def foo  
[15] pry(main)*     'bar'    
[15] pry(main)*   end    
[15] pry(main)* end  
=> :foo
[16] pry(main)> 
[17] pry(main)> class MyClass
[17] pry(main)*   include MyMethods  
[17] pry(main)* end  
=> MyClass
[18] pry(main)> MyClass.new.foo
=> "bar"

定義済み定数に対するメソッド

[1] pry(main)> module OrenoModule
[1] pry(main)*   MYCONST = 1
[1] pry(main)* end  
=> 1
# 定数の一覧を取得する
[2] pry(main)> OrenoModule.constants
=> [:MYCONST]
# 引数で指定した定数が定義されているかを調べる
[3] pry(main)> OrenoModule.const_defined?(:MYCONST)
=> true
# 定数の値を取得する
[4] pry(main)> OrenoModule.const_get(:MYCONST)
=> 1
# 定数の値を設定する
[5] pry(main)> OrenoModule.const_set(:MYCONST2, 2)
=> 2
[6] pry(main)> OrenoModule.const_get(:MYCONST2)
=> 2
# 定数の一覧を取得する
[7] pry(main)> OrenoModule.constants
=> [:MYCONST, :MYCONST2]
# 定数を取り除く
[1] pry(main)> class MyClass
[1] pry(main)*   MYCONST = 1
[1] pry(main)*   p remove_const(:MYCONST)
[1] pry(main)*   p MYCONST
[1] pry(main)* end  
1
NameError: uninitialized constant MyClass::MYCONST
# 祖先クラスを取得する
[2] pry(main)> Array.ancestors
=> [Array, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]

メソッドの設定

インスタンスメソッドの一覧や可視性等の操作には, 以下のメソッドを使う.

# インスタンスに設定されているメソッドの一覧を取得(grep して絞り込むの図)
[4] pry(main)> String.instance_methods.grep(/to_/)
=> [:to_i, :to_f, :to_s, :to_str, :to_sym, :to_r, :to_c, :to_enum]

# 可視性を定義済みのメソッドの可視性を後から変更することが可能(private から public に変更している)
[5] pry(main)> class MyClass
[5] pry(main)*   private
[5] pry(main)*   def foo
[5] pry(main)*     puts 'FOO'
[5] pry(main)*   end  
[5] pry(main)*   public :foo
[5] pry(main)* end  
=> MyClass
[6] pry(main)> my_class = MyClass.new
=> #<MyClass:0x0055cfea1ba070>
[7] pry(main)> my_class.foo
FOO
=> nil

インスタンスの属性としてインスタンス変数と読み取りメソッド, 書き込みメソッドを定義するには attr_accessor メソッド, attr_reader メソッド, attr_writer メソッド, attr メソッドが利用出来る.

[8] pry(main)> class MyClass
[8] pry(main)*   attr_accessor :height
[8] pry(main)* end  
=> nil
[10] pry(main)> my_class = MyClass.new
=> #<MyClass:0x0055cfeae4eac0>
[11] pry(main)> my_class.height = 200
=> 200
[12] pry(main)> my_class.height
=> 200

メソッドの別名設定は alias_method を利用する. alias との違いは alias_method はメソッドでありメソッド名を文字列で指定出来る.

[1] pry(main)> class MyClass
[1] pry(main)*   def foo
[1] pry(main)*     'foo'
[1] pry(main)*   end  
[1] pry(main)*   alias_method "original_foo", "foo"
[1] pry(main)*   def foo
[1] pry(main)*     'bar' + original_foo
[1] pry(main)*   end  
[1] pry(main)* end  
=> :foo
[2] pry(main)> m = MyClass.new
=> #<MyClass:0x00555d793c4700>
[3] pry(main)> m.foo
=> "barfoo"

eval / class_eval / class_exec

[1] pry(main)> Array.class_eval do
[1] pry(main)*   def foo
[1] pry(main)*     'bar'
[1] pry(main)*   end  
[1] pry(main)* end  
=> :foo
[2] pry(main)> [].foo
=> "bar"
[3] pry(main)> class MyClass
[3] pry(main)*   CONST = 1
[3] pry(main)* end  
=> 1
[4] pry(main)> MyClass.class_exec(3) {|i| puts i + self::CONST}
4
=> nil
[5] pry(main)> eval("puts 'Hello world'")
Hello world
=> nil
[6] pry(main)> "puts 'Hello world'"
=> "puts 'Hello world'"

クラス変数

クラス変数を扱うには, 以下のようなメソッドを利用する.

class_variables
class_variable_defined?
class_variable_get
class_variable_set
remove_class_variable

以下, 実行例の一部.

[1] pry(main)> class MyClass
[1] pry(main)*   @@foo = 1
[1] pry(main)* end  
=> 1
[2] pry(main)> MyClass.class_variables
=> [:@@foo]
[4] pry(main)> MyClass.class_variable_defined?(:@@foo)
=> true

モジュールの機能を取り込む

  • クラスやモジュール, オブジェクトにモジュールの機能を追加する include メソッドや extend メソッドを使う
  • include メソッドがクラスとそのインスタンスに機能を追加する
  • extend メソッドはそのオブジェクトのみに機能を追加する
[1] pry(main)> module MyMethods
[1] pry(main)*   def foo
[1] pry(main)*     'bar'
[1] pry(main)*   end  
[1] pry(main)* end  
=> :foo
[2] pry(main)> class MyClass
[2] pry(main)*   include MyMethods
[2] pry(main)* end  
=> MyClass
[3] pry(main)> MyClass.new.foo
=> "bar"
[4] pry(main)> class NewMyClass; end
=> nil
[5] pry(main)> n = NewMyClass.new
=> #<NewMyClass:0x00556bac384420>
[6] pry(main)> n.extend(MyMethods)
=> #<NewMyClass:0x00556bac384420>
[7] pry(main)> n.bar
NoMethodError: undefined method `bar' for #<NewMyClass:0x00556bac384420>
[8] pry(main)> n.foo
=> "bar"
[9] pry(main)> n1 = NewMyClass.new
=> #<NewMyClass:0x00556bab79f510>
[10] pry(main)> n1.foo
NoMethodError: undefined method `foo' for #<NewMyClass:0x00556bab79f510>

included メソッド及び extended メソッドはそれぞれ, include メソッドや extend メソッドによってそのモジュールの機能がクラスやモジュール, オブジェクトに取り込まれた時に実行されるメソッド.

[1] pry(main)> module MyModule
[1] pry(main)*   def self.included(object)  
[1] pry(main)*     p "#{object} has included #{self}"    
[1] pry(main)*   end    
[1] pry(main)* end  
=> :included
[2] pry(main)> 
[3] pry(main)> class MyClass
[3] pry(main)*   include MyModule  
[3] pry(main)* end  
"MyClass has included MyModule"
=> MyClass

include? はクラスやモジュールが指定されたモジュールをインクルードしているかどうかを確認する. included_modules はインクルードしているモジュールの一覧を取得する.

[4] pry(main)> MyClass.include?(MyModule)
=> true
[6] pry(main)> MyClass.included_modules
=> [MyModule, PP::ObjectMixin, Kernel]

モジュール関数

モジュールで定義されているメソッドを, モジュール関数として扱えるようにするには module_function メソッドを利用する.

[1] pry(main)> module MyMethods
[1] pry(main)*   def bar
[1] pry(main)*     puts "ok"
[1] pry(main)*   end  
[1] pry(main)*   module_function :bar
[1] pry(main)* end  
=> MyMethods
[2] pry(main)> MyMethods.bar
ok
=> nil

module_function メソッドの引数にメソッド名が指定された場合にはそのメソッドが, 指定されなければそれ以降に定義されたメソッドがモジュール関数となる.

祖先クラス

あるクラスの祖先クラスやインクルードしているモジュールの一覧を取得するには, Module.ancestors を利用する.

[3] pry(main)> MyMethods.ancestors
=> [MyMethods]
[4] pry(main)> Array.ancestors
=> [Array, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]

Enumerable モジュール

map メソッド

[1] pry(main)> [1, 2, 3, 4, 5].map {|i| i **2}
=> [1, 4, 9, 16, 25]

each_with_index メソッド

[2] pry(main)> [:a, :b, :c, :d, :e].each_with_index {|v, i| puts "#{v} => #{i}"}
a => 0
b => 1
c => 2
d => 3
e => 4
=> [:a, :b, :c, :d, :e]

inject メソッド, reduce メソッド

inject メソッドと reduce メソッドは同義.

[3] pry(main)> [1, 2, 3, 4, 5].inject(0) {|result, v| result + v ** 2}
=> 55
[4] pry(main)> [1, 2, 3, 4, 5].reduce(0) {|result, v| result + v ** 2}
=> 55

each_slice メソッド, each_cons メソッド

[5] pry(main)> (1..10).each_slice(3) {|items| p items}
[1, 2, 3]
[4, 5, 6]
[7, 8, 9]
[10]
=> nil

[7] pry(main)> (1..10).each_cons(3) {|items| p items}
[1, 2, 3]
[2, 3, 4]
[3, 4, 5]
[4, 5, 6]
[5, 6, 7]
[6, 7, 8]
[7, 8, 9]
[8, 9, 10]
=> nil

reverse_each メソッド

[8] pry(main)> [1, 2, 3, 4, 5].reverse_each {|i| puts i}
5
4
3
2
1
=> [1, 2, 3, 4, 5]

all? メソッドと any? メソッドと include? メソッド

# 全ての要素が真であれば true
[9] pry(main)> [1, nil, 3].all?
=> false
[11] pry(main)> [1, "a", 3].all?
=> true
# 真である要素が 1 つでもあれば true
[12] pry(main)> [1, nil, 3].any?
=> true
# 全ての要素が真であれば true
[13] pry(main)> [].all?
=> true
# 真である要素が 1 つでもあれば true
[14] pry(main)> [].any?
=> false
# 要素が含まれていれば true
[15] pry(main)> [1, 2, 3, 4, 5].include?(3)
=> true

find メソッドと detect メソッド

ブロックを評価して最初に真となる要素を返す.

# find メソッドはブロックを評価して最初に真となる要素を返す
[16] pry(main)> [1, 2, 3, 4, 5].find {|i| i % 2 == 0}
=> 2
# find_index は要素の代わりにインデックスを返す
[17] pry(main)> [1, 2, 3, 4, 5].find_index {|i| i % 2 == 0}
=> 1
# detect メソッドはブロックを評価して最初に真となる要素を返す
[18] pry(main)> [1, 2, 3, 4, 5].detect {|i| i % 2 == 0}
=> 2

find_all メソッドや select メソッド, reject メソッド

# find_all メソッドは, ブロックの評価結果が真となる全ての要素を返す
[19] pry(main)> [1, 2, 3, 4, 5].find_all {|i| i % 2 == 0}
=> [2, 4]
# select メソッドは, ブロックの評価結果が真となる全ての要素を返す
[20] pry(main)> [1, 2, 3, 4, 5].select {|i| i % 2 == 0}
=> [2, 4]
# reject メソッドは, ブロックの評価結果が偽となる全ての要素を返す
[21] pry(main)> [1, 2, 3, 4, 5].reject {|i| i % 2 == 0}
=> [1, 3, 5]

sort メソッドと sort_by メソッド

# sort メソッドは, 要素を <=> メソッドで比較して昇順にソートした配列を新たに生成する
[22] pry(main)> ["aaa", "b", "cc"].sort {|a, b| a.length <=> b.length}
# sort_by メソッドは, ブロックの評価結果を <=> メソッドで比較して昇順にソートした配列を使って, 元の配列をソートした新しい配列を生成する
=> ["b", "cc", "aaa"]
[23] pry(main)> ["aaa", "b", "cc"].sort_by {|a| a.length}
=> ["b", "cc", "aaa"

take メソッドと first メソッド

# take メソッドは先頭から指定した数の要素を配列として返す
[9] pry(main)> [:a, :b, :c].take(2)
=> [:a, :b]
# first メソッドは先頭から指定した数の要素を配列として返す(数を指定しない場合には, 先頭の要素のみ返す)
[10] pry(main)> [:a, :b, :c].first
=> :a

lazy メソッド

lazy メソッドは map メソッドや select メソッド等のメソッドが遅延評価を行うように再定義される.

[14] pry(main)> a = [1, 2, 3, 4, 5].lazy.select {|e| e % 2 == 0}
=> #<Enumerator::Lazy: ...>
[15] pry(main)> b = a.map {|e| e * 2}
=> #<Enumerator::Lazy: ...>
[16] pry(main)> c = a.take(3)
=> #<Enumerator::Lazy: ...>
[17] pry(main)> c.to_a
=> [2, 4]
[9] pry(main)> a = (1..10000000000).select {|e| e % 2 == 0}.take(5)
# lazy を利用すると一瞬で結果を得ることが出来る
[7] pry(main)> a = (1..10000000000).lazy.select {|e| e % 2 == 0}.take(5)
=> #<Enumerator::Lazy: ...>
[8] pry(main)> a.force
=> [2, 4, 6, 8, 10]
# 普通に select メソッドを実行するとかなーり遅い
[9] pry(main)> a = (1..10000000000).select {|e| e % 2 == 0}.take(5)

lazy メソッドについては以下の記事が参考になった.

その他

# max メソッドはそれぞれの要素の最大値を返す(min メソッドはそれぞれの要素の最小値を返す)
[24] pry(main)> (1..10).max {|a, b| (a % 5 + a) <=> (b % 5 +b)}
=> 9
# max_by メソッドはブロックの評価結果が最大であった要素を返す(min_by メソッドはブロックの評価結果が最小であった要素を返す)
[25] pry(main)> (1..10).max_by {|v| (v % 5 + v)}
=> 9
# map メソッドは、要素の数だけ繰り返しブロックを実行し、ブロックの戻り値を集めた配列を作成して返す
[1] pry(main)> (1..10).map {|v| v % 5 + v}
=> [2, 4, 6, 8, 5, 7, 9, 11, 13, 10]
# collect メソッドは map メソッドの別名
[2] pry(main)> (1..10).collect {|v| v % 5 + v}
=> [2, 4, 6, 8, 5, 7, 9, 11, 13, 10]
# count メソッドは要素数を返す
[3] pry(main)> (1..10).count
=> 10
# first メソッドは先頭の要素か, 引数があれば先頭からその数だけ要素を返す
[4] pry(main)> (1..10).first
=> 1
[5] pry(main)> (1..10).first(3)
=> [1, 2, 3]
# cycle メソッドは要素を先頭から順に取り出し, 末尾まで達したら再度先頭に戻り, これを繰り返す
[7] pry(main)> [:a, :b, :c].cycle {|v| p v}
:a
:b
:c
:a
...
# group_by メソッドは, ブロックの評価結果をキーとし, 同じキーを持つ要素を配列としたハッシュを返す
[7] pry(main)> (1..10).group_by {|v| v %2}
=> {1=>[1, 3, 5, 7, 9], 0=>[2, 4, 6, 8, 10]}
# zip メソッドは, 自身と引数に指定した配列から, 1 つずつ要素を取り出して配列を作り, それを要素とする配列を返す
[8] pry(main)> [:a, :b, :c].zip([1, 2, 3], ["a", "b", "c"])
=> [[:a, 1, "a"], [:b, 2, "b"], [:c, 3, "c"]]
# take_while メソッドは先頭からブロックを評価し, 最初に偽になった要素の直前まで返す
[11] pry(main)> [:a, :b, :c, :d, :e].take_while {|e| e != :d}
=> [:a, :b, :c]
# drop メソッドは take メソッドとは逆に, 先頭から指定した数の要素を取り除いた残りの要素を配列として返す
[12] pry(main)> [:a, :b, :c, :d, :e].drop(3)
=> [:d, :e]
# drop_while メソッドは先頭からブロックを評価し, 最初に偽になった要素の手前までを切り捨て, 残りの要素を配列として返す
[13] pry(main)> [:a, :b, :c, :d, :e].drop_while {|e| e != :c}
=> [:c, :d, :e]

Marshal モジュール

Marshal モジュールとは

  • Ruby のオブジェクトを文字列化し, ファイルに書きだしたり読み戻したりする
  • 文字列化したデータをマーシャルデータと呼ぶ

Marshal.dump で書き出す際の制限

以下のようなオブジェクトが指定された場合には, TypeError が発生する.

  • 名前のついていないクラスやモジュールでは, ArgumentError が発生する
  • システムがオブジェクトの状態を保持するような, IO クラス, Dir クラス, File クラス
  • MatchData クラス, Proc クラス, Thread クラス等のインスタンス
  • クラスメソッドを定義したオブジェクト
  • 上記のオブジェクトを間接的に指定しているクラスのオブジェクト(ex. 初期値をブロックで指定した Hash クラスのオブジェクト)

Marshal.dump

[1] pry(main)> Marshal.dump({:a => 1, :b => 3, :c => 5})
=> "\x04\b{\b:\x06ai\x06:\x06bi\b:\x06ci\n"
[2] pry(main)> file = File.open("marshaldata", "w+")
=> #<File:marshaldata>
[3] pry(main)> Marshal.dump({:a => 1, :b => 3, :c => 5}, file)
=> #<File:marshaldata>
$ cat marshaldata 
:ai:b:ci

Marshal.load

[1] pry(main)> m = Marshal.dump({:a => 1, :b => 3, :c => 5})
=> "\x04\b{\b:\x06ai\x06:\x06bi\b:\x06ci\n"
[2] pry(main)> Marshal.load(m)
=> {:a=>1, :b=>3, :c=>5}
[1] pry(main)> file = File.open("marshaldata", "w+")
=> #<File:marshaldata>
[2] pry(main)> Marshal.dump({:a => 1, :b => 3, :c => 5}, file)
=> #<File:marshaldata>
# ファイルから読み込み
[4] pry(main)> file = File.open("marshaldata", "r")
=> #<File:marshaldata>
# 読み込んだオブジェクトを Marshal.load する
[5] pry(main)> Marshal.load(file)
=> {:a=>1, :b=>3, :c=>5}

Thread クラス

スレッドの生成

[1] pry(main)> t = Thread.new { sleep 1 }
=> #<Thread:0x0055c888c34af0 sleep>
[2] pry(main)> t = Thread.new(3) {|t| sleep t }
=> #<Thread:0x0055c888b550d0 sleep>

new メソッド以外に startfork メソッドでもスレッドを生成することが出来る(startforkinitialize メソッドが呼ばれない)

スレッドの状態

スレッドの状態は status メソッドで調べることが出来る.

状態 詳細
run 実行中又は実行可能状態
sleep 一時停止状態
aborting 終了処理中
false kill メソッドで終了したり, 正常終了したスレッドの場合には false が返る
nil 例外等で異常終了したスレッドの場合には nil が返る
[4] pry(main)> t = Thread.new { sleep 100 }
=> #<Thread:0x0055c889646b10 sleep>
[5] pry(main)> t.status
=> "sleep"
[6] pry(main)> t.alive?
=> true

スレッド中の例外

  • スレッドの中で例外が発生したとき, その例外を rescue で捕捉しなかった場合には, 通常はそのスレッドのみが警告無しに終了される
  • 但し, そのスレッドを join メソッドで待っている他のメソッドがあった場合には, 待っているスレッドに対して同じ例外が再度発生する
[1] pry(main)> t = Thread.new { Thread.pass; raise "Raise exception" }
=> #<Thread:0x0055c2bb268120 dead>
[2] pry(main)> e = Thread.new do
[2] pry(main)*   begin  
[2] pry(main)*     t.join
[2] pry(main)*   rescue => ex  
[2] pry(main)*     puts ex.message
[2] pry(main)*   end  
[2] pry(main)* end  
Raise exception
=> #<Thread:0x0055c2bbcd0900 dead>

スレッドの中で例外が発生した場合に, プログラム自体を終了させるようにするには, 次の 3 つの方法がある.

  • Thread.abort_on_exception メソッドに true を設定する
  • 特定のスレッドの abort_on_exception メソッドに true を設定する
  • グローバル変数 $DEBUG を true にし, プログラムを -d オプション付きで実行する

Fiber クラス

Fiber クラスとは

  • 他の言語でコルーチンやセミコルーチンと呼ばれる軽量スレッドであるファイバーを提供するクラス
  • 明示的に指定しない限りコンテキストが切り替わらない, 親子関係を持つ等, Thread クラスが提供するスレッドとは異なる
[3] pry(main)> f = Fiber.new do
[3] pry(main)*   loop do  
[3] pry(main)*     puts "hello"    
[3] pry(main)*     puts "    child -> parent"    
[3] pry(main)*     Fiber.yield    
[3] pry(main)*   end    
[3] pry(main)* end  
=> #<Fiber:0x0055c2bbe2cd58>
[4] pry(main)> 3.times do
[4] pry(main)*   puts "    parent -> child"  
[4] pry(main)*   f.resume  
[4] pry(main)* end  
    parent -> child
hello
    child -> parent
    parent -> child
hello
    child -> parent
    parent -> child
hello
    child -> parent
=> 3
  • Fiber.new メソッドにブロックを渡すと, Fiber クラスのオブジェクトが生成される
  • 生成されたオブジェクトはインスタンスメソッドである resume メソッドでコンテキストが切り替わる
  • Fiber.yield メソッドで親である呼び出し元へコンテキストが切り替わる

以上

写経でした.