Ruby 技術者認定試験制度の改訂によりRubyバージョンが1.8から2.1へ変更となった。
変更点について確認して行きます。
ハッシュリテラル
〜: 値の書き方が変更点。もう結構お馴染みの書き方ですね。
最初は何だこれは??と思いましたが。。
**〜はオブション引数。引数に指定していないKeyが挿入された場合、まとめて格納してくれます。
def test(a: 1, b: 2, **c) p a p b p c end test(b: 3, c: 4, d: 5) #=> 1 #=> 3 #=> {:c=>4, :d=>5}
キーワード引数
2.1よりデフォルト値を指定しなくとも良くなった。
def calc(x:, y:, z:) x + y + z end calc(x:1, y:2, z:3) # => 6 # 余分なキーワードが存在する場合はArgumentError が発生 calc(x:1, y:2, z:3, a:5) #=> ArgumentError: unknown keyword: a
その他の引数との混在させる場合には定義順に注意が必要。下記の通り
1.通常の引数
2.デフォルト式付き引数
3.可変長引数
(4.通常の引数)
5.キーワード引数&デフォルト式付きキーワード引数
6.オプションハッシュ
7.ブロック引数
def method(a, b = 1, c*, d, e:, f: 2, **g, &h) return a, b, c, d, e, f, g, h end method(0, 1, 2, 3, e:4, f:5, g:6) { 7 } # => [0, 1, [2], 3, 4, 5, {:g=>6}, #<Proc:0x007fab1118ec28@(irb):7>]
通常引数はオプションハッシュの前であれば、どこかにいれることは可能かも。。
現実的にはデフォルト式付き引数とデフォルト式付きキーワード引数はどちらかに
統一したいですね。
新しいラムダ式
# 新しい書き方 ->(x) { puts x } # 上記と同じ古い書き方 lambda { |x| puts x }
# 引数なし -> { puts 'hello' } # 引数一つ -> (x) { puts x } -> x { puts x } # 引数二つ -> (x, y) { puts x, y } -> x, y { puts x, y } # オブジェクト化&呼び出し add = ->(x, y) { x + y } p add.call(1, 2) # => 3
文字数が減って、引数がカッコ内からの代入へ変更。
わかりやすくなったかな..
Enumerable#lazy
mapやselectにおけるlazy版を提供するメソッド
# 下記だと無限の長さの文字列を作成しようとして、結果が返却されない p (1..Float::INFINITY).map { |i| i ** 2 } # 遅延評価させるlazy版で記述 p (1..Float::INFINITY).lazy.map { |i| i ** 2 }.take(3).force #=> [1, 2, 3]
takeは引数で指定したn個の要素を取得する
forceはto_aのaliasで怠惰なEnumeratorに仕事を強制させるメソッド
mapと遅延評価版mapは返却するものが異なる
Enumerable#map -> Array
Enumerable::Lazy#map -> Enumerable::Lazy
参考URL
Rubyist Magazine - 無限リストを map 可能にする Enumerable#lazy
Ruby - EnumeratorとEnumerator::Lazyの違い - Qiita
Fiber
・Procのように手続きをオブジェクト化できる
・手続きを中断し、途中から再開できる
なんのこっちゃ、例をみてみます。
f = Fiber.new { x = 0 loop do # yeild実行後に呼び出し元へ戻る。戻り値はxになる Fiber.yeild(x) # 初回、この時点で x = 0、2回目の呼び出しで x = 1 x += 1 end } 3.times do # Fiber内のx += 1から開始する p f.resume end #=> 1 #=> 2 #=> 3 2.times do p f.resume end #=> 4 #=> 5
ノンプリエンプティブな軽量スレッドを提供する
ファイバーは親子関係を持つ。
Fiber#resume を呼んだファイバーが親になり 呼ばれたファイバーが子になる。
親子の切り替え方
- Fiber#resume により子へコンテキストを切り替える
- Fiber.yield により親へコンテキストを切り替える
スレッド間をまたがるファイバーの切り替えはできない。
f = nil Thread.new do f = Fiber.new{} end.join f.resume #=> t.rb:5:in `resume': fiber called across threads (FiberError)
Module#prepend
既存メソッドを上書きできるinclude機能を提供するのがprepend
例を見てみましょう。
まずはincludeの場合
module M def method puts "M method" end end class C include M def method puts "C method" end end c = C.new # クラス内のメソッドが優先される c.method # => C method
次はprependの場合
module M def method puts "M method" end end class C prepend M def method puts "C method" end end c = C.new # module内のメソッドが優先された!! c.method # => M method
Refinements
オープンクラス、モンキーパッチなどで全体へ影響する修正を行い、
面倒なことになるのを避けるために、限定的なスコープのみでクラス拡張を行えるのがRefinements
module M refine Fixnum do def /(other) self.quo(other) end end end p 1 / 2 # => 0 #usingで読み込んだスコープのみで有効、読み込んだ後から適応される using M p 1 / 2 # => (1/2)
usingのスコープ
ファイルをまたがないため、記述したファイルを require や load しても拡張は有効にならない。
Refinementは厳密なレキシカルスコープ(文脈上のスコープ)で動作する。
module Extensions1 refine String do def hello puts self + "hello Extensions1" end end end module Extensions2 refine String do def hello puts self + "hello Extensions2" end end end class RefineTest using Extensions1 "outer1".hello # => outer1 hello Extensions1 class InnerClass using Extensions2 # innerClass内のみで変更が適用される "inner".hello # => inner hello Extensions2 end "outer2".hello # => outer2 hello Extensions1 end
モジュールには適用できない
module M refine Enumrable do # => エラーになる end end
SelectorNamespace
class A def m1 puts "GOOD" end def m2 print "TEST " m1 end end a = A.new a.m1 # => GOOD a.m2 # => TEST GOOD module Extension refine A do def m1 "VERY GOOD" end end end using Extension a.m1 # => VERY GOOD a.m2 # => TEST GOOD
最後の行のa.m2 # => TEST GOODに関して
m2はExtension内で変更されていないためAクラス内のm2を参照する。
m2はAに属するメソッドなので内部で参照しているm1はAクラス内のm1を参照する。
Aのm2メソッドはExtension内のスコープに影響を受けない。
これをSelectorNamespaceと呼ぶらしい。