これは
- 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
メソッド以外に start
や fork
メソッドでもスレッドを生成することが出来る(start
と fork
は initialize
メソッドが呼ばれない)
スレッドの状態
スレッドの状態は 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 メソッドで親である呼び出し元へコンテキストが切り替わる
以上
写経でした.