気軽に楽しくプログラムと遊ぶ

自分が興味があってためになるかもって思う情報を提供しています。

Ruby Gold取得に近づくために1.8 → 2.1 の変更点を理解する

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と呼ぶらしい。

参考URL

http://qiita.com/yustoris/items/77fd309178dcdd13b5cd