これは
そして, この記事は
既に 2018 年だけど...
初老丸 Advent Calendar 2017 24 日目の記事です.
変数
初期化されていな時に参照した場合の動作
ローカル変数
- 参照箇所より前に代入文が記述してある場合は nil
- 代入文が記述されていない場合には例外発生
irb(main):001:0> p foo NameError: undefined local variable or method `foo' for main:Object
グローバル変数
irb(main):004:0* p $foo nil => nil
クラス変数
(irb):5: warning: class variable access from toplevel NameError: uninitialized class variable @@foo in Object
インスタンス変数
irb(main):007:0* p @foo nil => nil
定数
irb(main):009:0> p FOO NameError: uninitialized constant FOO
リテラル
Ruby のリテラル
- 数値
- 論理値
- 文字列
- シンボル
- 配列
- ハッシュ
- 範囲
- 正規表現
- コマンド出力
数値リテラル
有理数, 複素数のリテラル
irb(main):010:0> 42/10r => (21/5) irb(main):011:0> (42/10r).class => Rational irb(main):012:0> 3.14r => (157/50) irb(main):013:0> 42i => (0+42i) irb(main):014:0> 42i.class => Complex irb(main):015:0> 3.14i => (0+3.14i)
数値演算
- UFO 演算子
irb(main):016:0> 100 <=> 10 => 1 irb(main):017:0> 100 <=> 100 => 0 irb(main):018:0> 10 <=> 100 => -1
UFO 演算子と呼ばれる比較演算子は, 比較の結果を数値で返す. 左辺の値が右辺の値より大きければ 1 , 等しければ 0 , 左辺の値が小さければ -1 を返す.
- 自己代入演算子
irb(main):019:0> a = 100 => 100 irb(main):020:0> a += 1 => 101 irb(main):021:0> a -= 1 => 100 irb(main):022:0> a *= 1 => 100 irb(main):023:0> a *= 2 => 200 irb(main):024:0> a **= 2 => 40000
数値クラス
irb(main):025:0> 1.class => Fixnum irb(main):026:0> 1.class.superclass => Integer irb(main):027:0> 1.class.superclass.superclass => Numeric
ブロックと Proc
ブロックの基本
ブロックはメソッドを呼び出すときのみ記述でき, メソッド内部では, yield
という式を使用することでブロックの内部で記述した処理を呼び出すことが出来る.
def func x x + yield end p func(1){ 2 }
以下は実行例.
irb(main):028:0> def func x irb(main):029:1> x + yield irb(main):030:1> end => :func irb(main):031:0> p func(1){ 2 } 3 => 3
クロージャとしてのブロック
def func y y + yield end x = 2 p func(1) { x +=2 }
以下は実行例.
irb(main):032:0> def func y irb(main):033:1> y + yield irb(main):034:1> end => :func irb(main):035:0> x = 2 => 2 irb(main):036:0> p func(1) { x += 2 } 5 => 5 irb(main):037:0> p x 4 => 4
- ブロック外で変数 x に値 2 を代入している
- メソッド func にブロックを渡して yield によりブロックを実行
- ブロック実行した際に x の値を取得し更新する(この x はブロックの外の変数 x と同一の変数である)
- ブロック内で x を更新(x += 2)している為, func メソッドの実行後, x の値を参照すると値が更新されていることが分かる
ユーザーから直接指定できず, 変更も出来ないので, 代入ではなく束縛と呼ぶ. このように処理の生成時の環境を束縛するものを, 一般的にクロージャと呼ぶ.
ブロックフォーマット
- ブロックは引数を受け付けることができ, 波括弧
{}
又は do の後で引数リストを|
で囲む
def func a, b a + yield(b, 3) end p func(1, 2){|x, y| x + y}
以下, 実行例.
[1] pry(main)> def func a, b [1] pry(main)* a + yield(b, 3) [1] pry(main)* end => :func [2] pry(main)> p func(1, 2){|x, y| x + y} 6 => 6
上記の例では,
- func に 1 と 2 を引数として渡している
- func の内部では, 第一引数の値 1 とブロックの実行結果を合計する
- yield はブロック引数(2 と 3)の値を合計して 5 が返り, 実行結果は 6 となる
ブロックの判定
メソッドの内部でブロックが指定されたかどうかを判定する例.
def func return 1 if block_given? 2 end p func()[] p func
以下, 実行例.
[1] pry(main)> def func [1] pry(main)* return 1 if block_given? [1] pry(main)* 2 [1] pry(main)* end => :func [2] pry(main)> p func(){} 1 => 1 [3] pry(main)> p func 2 => 2
Proc
proc = Proc.new{|x| p x} proc.call(1)
以下, 実行例.
[4] pry(main)> proc = Proc.new{|x| p x} => #<Proc:0x00563922227520@(pry):7> [5] pry(main)> proc.call(1) 1 => 1
Proc とブロックの相互変換
- Proc からブロックへの変換
def func x x + yield end proc = Proc.new {2} func(1, &proc)
以下, 実行例.
[1] pry(main)> def func x [1] pry(main)* x + yield [1] pry(main)* end => :func [2] pry(main)> proc = Proc.new {2} => #<Proc:0x0056494c0d5098@(pry):4> [3] pry(main)> func(1, &proc) => 3
上記のように, Proc オブジェクトに &
を付けて最後の引数に指定する.
- ブロックから Proc への変換
def func x, &proc x + proc.call end func(1) do 2 end
以下, 実行例.
[4] pry(main)> def func x, &proc [4] pry(main)* x + proc.call [4] pry(main)* end => :func [5] pry(main)> func(1) do [5] pry(main)* 2 [5] pry(main)* end => 3
- ブロックを Proc オブジェクトとして受けるには, 最後の仮引数に
&
を付けた名前を指定する &
を付けた引数で Proc オブジェクトを参照出来る- 参照時は
&
を付けないこと
lambda
lambda メソッドは, Proc インスタンスを生成するが, Proc とは異なる動きをする.
lmb = lambda{|x| p x} # Ruby 1.9 から以下のような書き方も出来る lmb = ->(x){p x} lmb.call(1)
以下, 実行例.
[6] pry(main)> lmb = lambda{|x| p x} => #<Proc:0x0056494cbf9e60@(pry):12 (lambda)> [7] pry(main)> lmb.call(1) 1 => 1 [8] pry(main)> lmb = ->(x){p x} => #<Proc:0x0056494cc794d0@(pry):14 (lambda)> [9] pry(main)> lmb.call(1) 1 => 1
proc と lambda の違い
- Proc では生成元のスコープを脱出する
- lambda は, そのブロック内で return すると呼び出し元に復帰する
# proc 中のリターン def func proc = Proc.new{return 1} proc.call 2 end p func # lambda 中のリターン def func proc = lambda{return 1} proc.call 2 end p func
以下, 実行例.
[10] pry(main)> def func [10] pry(main)* proc = Proc.new{return 1} [10] pry(main)* proc.call [10] pry(main)* e [10] pry(main)* end => :func [11] pry(main)> p func 1 => 1 [12] pry(main)> def func [12] pry(main)* proc = lambda{return 1} [12] pry(main)* proc.call [12] pry(main)* 2 [12] pry(main)* end => :func [13] pry(main)> p func 2 => 2
以下の通り, 引数が一致しない場合, メソッドでは例外 ArgumentError が発生するが, Proc とブロックでは無視するか, nil を代入する.
[14] pry(main)> def func x [14] pry(main)* x [14] pry(main)* end => :func [15] pry(main)> func ArgumentError: wrong number of arguments (0 for 1) from (pry):28:in `func' [16] pry(main)> p1 = Proc.new{|x, y| y} => #<Proc:0x0056494cf472c0@(pry):32> [17] pry(main)> p p1.call(1) nil => nil [18] pry(main)> p p1.call(1, 2) 2 => 2
lambda の場合には, 以下のようにメソッドと同じ動きになる.
[19] pry(main)> p1 = lambda{|x, y| y} => #<Proc:0x0056494cd82278@(pry):35 (lambda)> [20] pry(main)> p p1.call(1) ArgumentError: wrong number of arguments (1 for 2) from (pry):35:in `block in __pry__' [21] pry(main)> p p1.call(1, 2) 2 => 2
-> を用いた lambda 記法
Ruby 1.9 以降から lambda を ->
の形式で書くことが出来る.
p1 = ->(x, y){p x + y} p p1.call(x, y)
以下, 実行例.
[22] pry(main)> p1 = ->(x, y){p x + y} => #<Proc:0x0056494cbfa040@(pry):38 (lambda)> [23] pry(main)> p p1.call(1, 2) 3 3 => 3
ブロックを受け付けるメソッド
配列の each メソッド
[1, 2, 3].each do |value| p value end # 同義 [1, 2, 3].each {|value| p value}
配列のインデックスが必要な場合には, each_with_index メソッドが使用可能.
[3, 4, 5].each_with_index do |value, index| puts index + value end # 同義 [3, 4, 5].each_with_index {|value, index| puts index + value}
引数を二つとり, 第二引数にインデックスが指定される.
ハッシュの each メソッド
{:a => 1, :b => 2}.each do |key, value| p "#{key}:#{value}" end # 同義 {:a => 1, :b => 2}.each {|key, value| p "#{key}:#{value}"}
キーのみ必要な場合 each_key メソッド, 値のみ必要な場合は each_value メソッドが使用出来る.
# each_key {:a => 1, :b => 2}.each_key do |key| p "#{key}" end # 同義 {:a => 1, :b => 2}.each_key {|key| p "#{key}"} # each_value {:a => 1, :b => 2}.each_value do |value| p "#{value}" end # 同義 {:a => 1, :b => 2}.each_value {|value| p "#{value}"}
範囲オブジェクトの each メソッド
("a".."d").each do |value| p value end # 同義 ("a".."d").each {|value| p value}
範囲を指定したループでは, 値を増やしていく upto メソッドや値を減らしていく downto メソッドもある.
2.upto(4) do |i| p i end # 同義 2.upto(4) {|i| p i} # 実行例 [34] pry(main)> 2.upto(4) {|i| p i} 2 3 4 5.downto(1) do |i| p i end # 同義 1 5.downto 1 do |i| p i end # 同義 2 5.downto(1) {|i| p i} # 実行例 [35] pry(main)> 5.downto(1) {|i| p i} 5 4 3 2 1
do ~ end と {}
do ~ end より {} の方が結合が強い
うっかり do ~ end
と {}
が同義だと思い込んでいたけど, るりまやこちらを読ませて頂いて, 以下のような違いがあるようだ.(「るりま」より引用)
{ ... } の方が do ... end ブロックよりも強く結合します。 次に例を挙げますが、このような違いが影響するコードは読み辛いので避けましょう:
foobar a, b do .. end # foobarの引数はa, bの値とブロック foobar a, b { .. } # ブロックはメソッドbの引数、aの値とbの返り値とがfoobarの引数
今までは, コードを見易くする為だけに使い分けをしてきた気がするけど, 以下のように, 用途毎に書き分けるようにしたいと思う.(こちらより引用)
{}
- ブロック付きメソッドがインラインの場合
- ブロック付きメソッドの戻り値を利用する場合
- ブロック付きメソッドからさらにメソッドチェーンする場合
- リソース管理のためにブロックを使う場合
do ~ end
- 上記以外
スレッド
Thread クラス
Thread クラスをインスタンス化することで新しいスレッドを生成することが出来る.(new や start 及び fork でインスタンスを生成する)
t = Thread.new do p "start" sleep 5 p "end" end p "wait" t.join
生成したインスタンスの join メソッドによって, スレッドの終了を待つことが出来る.(join メソッドでスレッドの終了を待つ)
ファイバ
Thread クラスとの相違点
- Thread は平行処理しているタスクの切り替えを OS や仮想マシンが行う
- ファイバは切り替えのタイミングを開発者がプログラム内で明示的に行う
Fiber
f = Fiber.new do (1..3).each do |i| Fiber.yield i end nil end p f.resume
以下, 実行例.
[48] pry(main)> f = Fiber.new do [48] pry(main)* (1..3).each do |i| [48] pry(main)* Fiber.yield i [48] pry(main)* end [48] pry(main)* nil [48] pry(main)* end => #<Fiber:0x0056494c6db460> [49] pry(main)> p f.resume 1 => 1 [50] pry(main)> p f.resume 2 => 2 [51] pry(main)> p f.resume 3 => 3 [52] pry(main)> p f.resume nil => nil [53] pry(main)> p f.resume FiberError: dead fiber called from (pry):83:in `resume'
ファイバへのコンテキストを切り替えるには resume メソッドを利用する. resume を呼び出すと, 対象のファイバ内の処理を終了するか, Fiber.yield が呼び出されるまで, ファイバ内のよりを実行する.
脱出構文と例外処理, 大域脱出
脱出構文
next
10.times do |i| next if i == 5 print i, "" end
以下, 実行例.
[1] pry(main)> 10.times do |i| [1] pry(main)* next if i == 5 [1] pry(main)* print i, "" [1] pry(main)* end 012346789=> 10
i が 5 の時, next が実行されて次のループに進む.
redo
10.times do |i| redo if i == 5 print i, "" end
以下, 実行例.
[1] pry(main)> 10.times do |i| [1] pry(main)* redo if i == 5 [1] pry(main)* print i, "" [1] pry(main)* end 01234 ... #=> 無限ループ
i が 5 の時, i が 5 のループをやり直す為, 4 を表示した後で無限ループとなる.
例外処理
raise
- 例外を発生させる為には, raise を使用する
- raise は第一引数に例外クラス又はそのインスタンスを, 第二引数にメッセージを指定する
raise ArgumentError, "引数が不正です" raise ArgumentError.new, "引数が不正です"
以下, 実行例.
[3] pry(main)> raise ArgumentError, "引数が不正です" ArgumentError: 引数が不正です [4] pry(main)> raise ArgumentError.new, "引数が不正です" ArgumentError: 引数が不正です
例外クラスのコンストラクタではメッセージを指定出来るので, 以下のように書くことも可能.
err = ArgumentError.new("引数が不正です") raise err
以下, 実行例.
[5] pry(main)> err = ArgumentError.new("引数が不正です") => #<ArgumentError: 引数が不正です> [6] pry(main)> raise err ArgumentError: 引数が不正です
例外クラスのインスタンスを省略した場合は, RuntimeError クラスの例外が発生する.
[8] pry(main)> raise "実行中にエラーが発生しました." RuntimeError: 実行中にエラーが発生しました.
begin ~ end
処理を中断させずに続行するには, 例外処理を記述する必要がある. 例外が発生する可能性がある箇所を begin ~ end で囲み, その中の rescue という節に例外処理を記述する.
begin 1 / 0 p 1 rescue p 0 end
以下, 実行例.
[9] pry(main)> begin [9] pry(main)* 1 / 0 # 例外が発生 [9] pry(main)* p 1 [9] pry(main)* rescue [9] pry(main)* p 0 # 実行される [9] pry(main)* end 0 => 0
else 節と ensure 節
resucue 節に続いて else 節を指定することで, 例外が発生しなかった時の処理を記述出来る. ensure 節を設けることで, 例外の発生に関わらず, 必ず実行する処理も追加出来る.
begin p 1 rescue p 0 else p 2 ensure p 3 end
以下, 実行例.
[10] pry(main)> begin # begin 節は実行される [10] pry(main)* p 1 [10] pry(main)* rescue # 例外は発生しないので, rescue 節は実行しない [10] pry(main)* p 0 [10] pry(main)* else # rescue 節が実行されないので else 節は実行される [10] pry(main)* p 2 [10] pry(main)* ensure # ensure 節は必ず実行される [10] pry(main)* p 3 [10] pry(main)* end 1 2 3 => 2
rescue は, begin 節を指定しなくても使用出来る. rescue 節は, if 式と同様に修飾子として書くことが出来る.
[12] pry(main)> 1 / 0 rescue p 1 1 => 1
例外クラスを指定した捕捉
例外クラスの階層
- Exception
- ScriptError
- SyntaxError
- Signal Exception
- StandardError
- ArgumentError
- RuntimeError
- NameError => NoMethodError
- ZeroDivisionError
- ScriptError
主な例外クラスは以下の通り.
例外クラス名 | 発生する場面の例 |
---|---|
SyntexError | 文法エラーがあった場合 |
SignalException | 捕捉していないシグナルを受けた場合 |
ArgumentError | 引数の数が合わない場合や値が正しくない場合 |
RuntimeError | 特定の例外クラスには該当しないエラーが発生した場合や例外クラスを省略した raise の呼び出し |
NameError | 未定義のローカル変数や定数を参照した場合 |
NoMethodError | 未定義のメソッドを呼び出した場合 |
ZeroDivisionError | 整数に対して整数の 0 で除算を行った場合 |
- 各例外クラスを rescue に続けて指定することで, それ自身か, そのサブクラスを捕捉することが出来る
- 例外クラスに続いて
=>
で識別子を指定すると, 例外オブジェクトを参照出来る - 例外オブジェクトは, message メソッドを使用して指定した例外メッセージを参照出来る
- 同様に backtrace オブジェクトを使用して例外が発生した場所を参照出来る
begin 1/0 rescue ZeroDivisionError => e p e p e.message p e.backtrace end
以下, 実行例.
$ ruby 3-161.rb #<ZeroDivisionError: divided by 0> "divided by 0" ["3-161.rb:2:in `/'", "3-161.rb:2:in `<main>'"]
- 同じスレッドとブロックで発生した最後の例外は, 組み込み関数
$!
で参照することが出来る - raise メソッドを引数無しで実行することで, 最後に発生した例外を再度発生させることが出来る
begin 1/0 rescue ZeroDivisionError => e p $! raise end
以下, 実行例.
$ ruby 3-162.rb #<ZeroDivisionError: divided by 0> 3-162.rb:2:in `/': divided by 0 (ZeroDivisionError) from 3-162.rb:2:in `<main>'
catch/throw による大域脱出
階層の深いループの中で全ての処理が完了した場合のように, 正常時であっても処理を抜けたい場合に catch と throw をペアで利用する.
def foo throw :exit end catch(:exit) { foo p 1 }
- throw が例外の場合の raise に相当し, catch が begin 節に相当すると考える
- throw が実行されると, 同盟のラベルが指定されている catch まで呼び出しスタックを辿り, ラベルが見つかった場合には, そのブロック内における後続の処理をスキップする
- ラベルには, シンボルの他に文字列を指定出来る
- 対応するラベルが見つからない場合は, NameError 例外が発生する
問題
多重代入
以下を実行すると何が表示されるか.
x, *y = *[0, 1, 2] p x, y
- 左辺の x には, 最初の要素が格納される
- y には
*
が付いているので, 残りの要素が配列として格納される
実行例.
[3] pry(main)> x, *y = *[0, 1, 2] => [0, 1, 2] [4] pry(main)> p x,y 0 [1, 2] => [0, [1, 2]]
数値リテラルの演算
実行結果が 0.8
とならないコードはどれか.
- 4/5
- 4.0/5
- 4/5r
4/5.0
Integer 同士の演算は Integer となり, 小数点以下は丸められる
- Integer と Rational クラスの演算は Rational クラスのオブジェクトを生成する((4/5r).to_f とすれば 0.8 になる)
実行例.
[5] pry(main)> 4/5 => 0 [6] pry(main)> 4.0/5 => 0.8 [7] pry(main)> 4/5r => (4/5) [8] pry(main)> 4/5.0 => 0.8
例外
以下のコードを実行するとどうなるか.
class Err1 < StandardError; end class Err2 < Err1; end begin raise Err2 rescue => e puts "StandardError" rescue Err2 => ex puts ex.class end
- begin 節の raise で発生する例外オブジェクトのクラスは Err2
rescue => e
にて, StandardError から派生する全ての例外を対象とする為,StandardError
が出力される
以下, 実行例.
[1] pry(main)> class Err1 < StandardError; end => nil [2] pry(main)> class Err2 < Err1; end => nil [3] pry(main)> begin [3] pry(main)* raise Err2 [3] pry(main)* rescue => e [3] pry(main)* puts "StandardError" [3] pry(main)* rescue Err2 => ex [3] pry(main)* puts ex.class [3] pry(main)* end StandardError => nil
定数の更新
以下のコードを実行するとどうなるか.
class C VAR = 0 def VAR=v VAR = v end def VAR VAR end end c = C.new c.VAR = 3 puts c.VAR
- メソッド内の定数更新はコンパイルエラーとなる
以下, 実行例.
$ ruby kihon-5.rb kihon-5.rb:4: dynamic constant assignment VAR = v ^
ブロック
以下のコードの中で文法として正しいものを選ぶ.
1.upto 5 do |x| puts x end
1.upto(5) do |x| puts x end
1.upto 5 {|x| puts x }
1.upto(5) {|x| puts x }
- ブロック引数を { ... } で囲む場合には, 引数の
()
を省略出来ない - do ... end で囲む場合には,
()
を省略することが出来る
以下, 実行例.
[1] pry(main)> 1.upto 5 do |x| [1] pry(main)* puts x [1] pry(main)* end 1 2 3 4 5 => 1 [2] pry(main)> 1.upto(5) do |x| [2] pry(main)* puts x [2] pry(main)* end 1 2 3 4 5 => 1 [3] pry(main)> 1.upto 5 {|x| puts x } SyntaxError: unexpected '{', expecting end-of-input 1.upto 5 {|x| puts x } ^ [3] pry(main)> 1.upto(5) {|x| puts x } 1 2 3 4 5 => 1
Proc
以下の実行結果となるように適切なコードを選ぶ.
<p>Hello, World.</p>
適切なコード(1).
def tag(name) puts "<#{name}>#{yield}</#{name}>" end tag(:p) {"Hello, world"}
適切なコード(2).
def tag(name, &block) puts "<#{name}>#{block.call}</#{name}>" end tag(:p) {"Hello, world"}
ブロック付きメソッドから呼び出し元ブロックを実行するには...
- yield を使う
- 引数に & を付けた変数を定義して, ブロックを Proc オブジェクトとして取得してから Proc#call を呼び出す
以下, 実行例.
[4] pry(main)> def tag(name) [4] pry(main)* puts "<#{name}>#{yield}</#{name}>" [4] pry(main)* end => :tag [5] pry(main)> tag(:p) {"Hello, world"} <p>Hello, world</p> => nil [6] pry(main)> def tag(name, &block) [6] pry(main)* puts "<#{name}>#{block.call}</#{name}>" [6] pry(main)* end => :tag [7] pry(main)> tag(:p) {"Hello, world"} <p>Hello, world</p> => nil
可変長引数
以下の実行結果となるように適切なコードを選ぶ.
[1, 2, 3]
適切なコード.
def hoge(*args) p *args end hoge [1, 2, 3]
*
が付いたメソッド引数は可変長引数になる- hoge に配列 [1, 2, 3] を渡すと, args[0] に格納されるので, 実行結果の出力を得る為には
*args
又はargs[0]
を指定する
以下, 実行例.
[8] pry(main)> def hoge(*args) [8] pry(main)* p *args [8] pry(main)* end => :hoge [9] pry(main)> hoge [1, 2, 3] [1, 2, 3] => [1, 2, 3] [10] pry(main)> def hoge(*args) [10] pry(main)* p args[0] [10] pry(main)* end => :hoge [11] pry(main)> hoge [1, 2, 3] [1, 2, 3] => [1, 2, 3]
キーワード引数
以下の実行結果となるように適切なコードを選ぶ.
1, 2, 3
適切なコード.
def hoge(x:, y: 2, **params) puts "#{x}, #{y}, #{params[:z]}" end hoge x:1, z:3
- キーワード引数
- 引数前に
**
をつなげることで, 明示的に定義したキーワード以外の引数を Hash オブジェクトとして受け取ることが出来る
以下, 実行例.
[12] pry(main)> def hoge(x:, y: 2, **params) [12] pry(main)* puts "#{x}, #{y}, #{params[:z]}" [12] pry(main)* end => :hoge [13] pry(main)> hoge x:1, z:3 1, 2, 3 => nil
lamda 式
以下の実行結果となるように適切なコードを選ぶ.
"Hello World"
適切なコード.
hi = ->(x){ puts "Hello, #{x}" } p hi.call("World")
以下, 実行例.
[14] pry(main)> hi = ->(x){ puts "Hello, #{x}" } => #<Proc:0x005643d17ebb28@(pry):28 (lambda)> [15] pry(main)> p hi.call("World") Hello, World nil => nil
以上
「Ruby 技術者認定試験合格教本」第三章を中心に写経したメモでした.