Ruby 1.8.7で使えるようになったRuby 1.9のメソッドたち

Ruby 1.8.7ではRuby 1.9からのbackportがとても多い。つまり、Ruby 1.9のあのメソッドがRuby 1.8でも使えるようになったということだ!!
これがすごいという機能がもりだくさん、ちょっと大人になったRuby 1.8をお楽しみに。

Enumeratorは組み込みになり、eachなどのイテレータメソッドはブロックをつけないとEnumerable::Enumeratorを返すようになった。おかげでブロック付きメソッドの柔軟性が飛躍的にアップ!

expectationsテスティングフレームワークによるテストで書いているので「gem install expectations」してから実行してみよう。手軽にユニットテストが書けるからおすすめ。書式は…見ればわかるよねw

ChangeLogで現在からRuby 1.8.6リリースまでを読んだので、ほとんどカバーしていると思われる。つかれた…

#!/usr/local/bin/ruby -wKe
# -*- coding: euc-jp -*-
# update (find-memofile "hatena/2008-05-08.txt")

require 'rubygems'
require 'expectations'

# `gem install expectations'
# Wed May  7 08:46:44 2008  Yukihiro Matsumoto  <[email protected]> まで

Expectations do
  # おいおい、見ろよ。Ruby 1.8.7だとブロックにブロックを渡せるんだぜ!
  expect "block is passed" do
    mod = Module.new do
      define_method(:foo) do |&block|
        block.call
      end
    end
    extend(mod).foo { "block is passed" } # !> method redefined; discarding old expects
  end
 

  # Symbol#to_procってすげえええー!
  expect [3, 3, 3, 5] do
    %w[foo bar baz fobar].map(&:length)
  end
  expect [1, 3] do
    [1,2,3,4].select(&:odd?)
  end

  # Ruby 1.9最強の萌えメソッドObject#tapがついに使えるようになったぜ!
  expect(:two=>2, :one=>1) do
    {}.tap{|h| h[:one]=1; h[:two]=2 }
  end

  # Object#instance_execキターーーーーー(゜∀゜)ーーーーーー!!
  expect 3 do
    "foo".instance_exec do
      length
    end
  end
  expect "foobar" do
    "foo".instance_exec("bar") do |x|
      self + x
    end
  end
  expect "FOO" do
    mod = Module.new do
      def def_each(*methods, &block)
        methods.each do |meth|
          define_method(meth) do
            instance_exec(meth, &block)
          end
        end
      end
    end

    klass = Class.new do
      extend mod
      def_each :foo, :bar do |meth|
        meth.to_s.upcase
      end
    end
    klass.new.foo
  end

  expect [5, 7] do
    a = 5
    b = 7
    instance_exec(a, b) do |x, y|
      [x, y]
    end
  end

  # Module#module_exec (class_exec) もよろしく。
  # instance_execがinstance_evalの進化形に対して、module_execはmodule_evalの進化形。
  expect 8 do
    klass = Class.new
    klass.module_exec(7) do |v|
      define_method(:hoge) { v+1 }
    end
    klass.new.hoge
  end

  # Binding#evalはeval(expr, binding)と等価。
  expect :in_block do
    bind = Object.new.instance_eval do
      var = :in_block
      binding
    end
    bind.eval("var")
  end

  # __method__はメソッド名を返す疑似変数
  expect :meth do
    mod = Module.new do
      def meth
        __method__
      end
    end
    extend(mod).meth
  end

  # MatchData#inspectがわかりやすくなった。これで正規表現のデバッグも楽になるよね。
  expect '#<MatchData "abc" 1:"a" 2:"b" 3:"c">' do
    "abc".match( /(.)(.)(.)/ ).inspect
  end

  # Method#receiverはレシーバ
  expect "recv" do
    "recv".method(:length).receiver
  end

  # Method#nameはメソッド名
  expect "length" do
    "recv".method(:length).name
  end

  # Method#ownerはメソッドのクラス名
  expect String do
    "recv".method(:length).owner
  end

  # UnboundMethod#nameはメソッド名
  expect "length" do
    String.instance_method(:length).name
  end

  # UnboundMethod#ownerはメソッドのクラス名
  expect String do
    String.instance_method(:length).owner
  end

  # GC.stress / GC.stress= も使えるようになった。

  # Array#flattenの引数で平滑化レベルを指定できるようになった。便利!
  expect [1, 2, [3]] do
    [1, [2, [3]]].flatten(1)
  end
  expect [1, 2, 3] do
    [1, [2, [3]]].flatten(2)
  end

  # Array#shuffle, Array#shuffle!が使えるようになった。
  expect [3,2,1,4] do
    srand 10                    # 乱数の種を指定して常に同じ結果を得るように。
    [1,2,3,4].shuffle
  end
  expect [3,2,1,4] do
    srand 10                    # 乱数の種を指定して常に同じ結果を得るように。
    a = [1,2,3,4]
    a.shuffle!
    a
  end

  # Array#sampleでランダムな要素を得る。
  expect 4 do
    srand 10
    [1,2,3,4].sample
  end

  expect [4,1] do
    srand 10
    [1,2,3,4].sample(2)
  end
  # Array#permutationは順列を得る。ブロックをつけないとEnumeratorになる。
  expect Enumerable::Enumerator do
    [1,2,3].permutation.class
  end
  expect [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]] do
    [1,2,3].permutation.sort.to_a
  end
  expect [[1],[2],[3]] do
    [1,2,3].permutation(1).sort.to_a
  end
  expect [[1,2],[1,3],[2,1],[2,3],[3,1],[3,2]] do
    [1,2,3].permutation(2).sort.to_a
  end

  # Array#combinationは組み合わせ。ああ、ガキのころに習った確率統計がなつかしい。
  expect Enumerable::Enumerator do
    [1,2,3,4].combination(3).class
  end
  expect [[1, 2, 3], [1, 2, 4], [1, 3, 4], [2, 3, 4]] do
    [1,2,3,4].combination(3).to_a
  end
  
  # Array#productは直積集合。
  expect [[1, 4], [1, 5], [2, 4], [2, 5], [3, 4], [3, 5]] do
    [1,2,3].product([4,5])
  end
  expect [[1, 1], [1, 2], [2, 1], [2, 2]] do
    [1,2].product([1,2]) # !> method redefined; discarding old next
  end

  # Array#pop, Array#shiftは取り除く要素数を指定できるようになった。
  expect [3, 4] do
    [1,2,3,4].pop(2)
  end
  expect [1,2] do
    a = [1,2,3,4]
    a.pop(2)
    a
  end
  expect [1, 2] do # !> method redefined; discarding old rewind
    [1,2,3,4].shift(2)
  end
  expect [3,4] do
    a = [1,2,3,4]
    a.shift(2)
    a
  end

  # Array#index, Array#rindexで条件を示すブロックが指定できるようになった。
  expect 1 do
    [0, 1, 0, 1, 0].index {|v| v > 0}
  end
  expect 3 do
    [0, 1, 0, 1, 0].rindex {|v| v > 0}
  end

  # Array#assocがto_aryを使うようになった。
  expect [1,2] do
    obj = Object.new
    def obj.to_ary() [1,2] end
    [obj].assoc 1
  end


  # Enumerable#takeは最初のn個を取り出す。
  expect [1, 2] do
    [1,2,3,4].take(2)
  end

  # Enumerable#take_whileは条件を満たす間要素を取り出す。
  expect [1, 2] do
    [1,2,3,4,5,6].take_while {|i| i < 3 }
  end
  expect [] do
    [1,2,3,4,5,6].take_while {|i| i > 3 }
  end
  
  # Enumerable#dropは前のn個を取り除いた新しい配列を返す。
  expect [3] do
    [1,2,3].drop(2)
  end

  # Enumerable#drop_whileは条件を満たす間の要素を取り除いた配列を返す。
  expect [3, 4, 5, 0] do
    [1, 2, 3, 4, 5, 0].drop_while {|i| i < 3 }
  end
  expect [1, 2, 3, 4, 5, 0] do
    [1, 2, 3, 4, 5, 0].drop_while {|i| i > 3 }
  end


  # take, take_while, drop, drop_while はEnumerableでも使える。
  expect [1, 2] do
    (1..6).take(2)
  end
  expect [1, 2] do
    (1..6).take_while {|i| i < 3 }
  end
  expect [3, 4, 5, 6] do
    (1..6).drop(2)
  end
  expect [3, 4, 5, 6] do
    (1..6).drop_while {|i| i < 3 }
  end


  # Enumerable#one?は条件を満たすもの(真)がひとつである場合にtrueとなる。
  expect true do
    %w{ant bear cat}.one? {|word| word.length == 4}
  end
  expect false do
    %w{ant bear cat}.one? {|word| word.length >= 3}
  end
  expect false do
    [ nil, true, 99 ].one?
  end
  expect true do
    [ nil, true, false ].one?
  end

  # Enumerable#none?は条件を満たすもの(真)がない場合にtrueとなる。
  expect true do
    %w{ant bear cat}.none? {|word| word.length == 5}
  end
  expect false do
    %w{ant bear cat}.none? {|word| word.length >= 4}
  end
  expect true do
    [].none?
  end
  expect true do
    [nil].none?
  end
  expect true do
    [nil,false].none?
  end


  # Enumerable#minmaxは最小値、最大値を同時に得る。
  expect [1, 6] do
    (1..6).minmax
  end

  # Enumerable#min_by, Enumerable#max_by, Enumerable#minmax_by はブロック評価結果で最小値、最大値をもとめる。
  expect 22 do
    [18, 15, 22, 53].min_by {|x| x % 10 }
  end
  expect 18 do
    [18, 15, 22, 53].max_by {|x| x % 10 }
  end
  expect [22, 18] do
    [18, 15, 22, 53].minmax_by {|x| x % 10 }
  end


  # Enumerable#cycleは要素ごとに無限に繰り返す。ブロックをつけないとEnumeratorになる。
  expect Enumerable::Enumerator do
    [1,2,3].cycle
  end
  # ちなみにEnumerable#takeは最初の要素n個取り出す。
  expect [1,2,3, 1,2,3, 1,2,3, 1] do
    [1,2,3].cycle.take(10)
  end
  # 繰り返す回数を指定できる。
  expect Enumerable::Enumerator do
    [1,2,3].cycle(3)
  end
  expect [1, 2, 3, 1, 2, 3, 1, 2, 3] do
    [1,2,3].cycle(3).to_a
  end


  # Enumerable#find_indexは条件を満たす最初のインデックスを求める。
  expect nil do
    (1..10).find_index  {|i| i % 5 == 0 and i % 7 == 0 }
  end
  expect 34 do
    (1..100).find_index {|i| i % 5 == 0 and i % 7 == 0 }
  end


  # Enumerable#injectでついにSymbolを指定することができるようになったぜ!
  # 合計が簡単に記述できるようになってウハウハ。
  # しかもreduceというこれまたカッコイイ別名を手に入れたぜ。MapReduceと対になれたよ。
  expect 10 do
    (1..4).inject(:+)
  end
  expect 10 do
    (1..4).reduce(:+)
  end

  
  # Enumerable#countは要素数、条件を満たす要素数の数を数える。
  # [2008/05/15] ブロックつきArray#nitemsは削除された。
  expect 2 do
    [1, 2, 4, 2, 10, 9].count(2)
  end
  expect 3 do
    [1, 2, 4, 2, 1, 1].count {|x| x%2 == 0}
  end

  # Enumerable#first。
  expect 1 do
    (1..6).first
  end


  # Enumerable#group_byはブロックの値をキーとするハッシュにグループ分けする。便利〜
  expect({0=>[3, 6], 1=>[1, 4], 2=>[2, 5]}) do
    (1..6).group_by {|i| i%3 }
  end

  # Enumerable::Enumerator#with_indexがあればどんなイテレータもwith_index版になる魔法のメソッド。
  expect [["abc\n", 0], ["def", 1]] do
    "abc\ndef".each_line.with_index.to_a
  end


  # Enumerable::Enumerator#nextは次の要素を順次得る。
  expect [1, 2]  do
    e = (1..6).each
    [e.next, e.next]
  end

  # Enumerable::Enumerator#rewindは最初の要素に巻戻す。
  expect [1, 2]  do
    e = (1..6).each
    e.next;  e.next
    e.rewind
    [e.next, e.next]
  end

  # String#lines, String#bytesはEnumeratorを返す。これがRuby 1.9流だ。
  expect ["abc\n", "def\n"] do
    "abc\ndef\n".lines.to_a
  end
  expect [97, 98, 99] do
    "abc".bytes.to_a
  end


  # String#chars, String#each_charが使えるようになった。
  # もはやsplit(//)なんて書かなくてもよくなった。
  expect ["お", "は", "よ", "う"] do
    "おはよう".chars.to_a
  end
  expect ["お", "は", "よ", "う"] do
    [].tap{|a| "おはよう".each_char{|c| a << c}}
  end

  
  # String#partition, String#rpartitionはセパレータ前、セパレータ、セパレータ後を返す。$KCODEに対応している。
  expect ["これ", "は", "ペンです"] do
    "これはペンです".partition("は")
  end
  expect ["123", "|", "456|789"] do
    "123|456|789".partition("|")
  end
  expect ["123|456", "|", "789"] do
    "123|456|789".rpartition("|")
  end


  # String#start_with?, String#end_with? は始まり・終わりの文字列の検査。
  expect true do
    "あいうえお".start_with? "あい"
  end
  expect true do
    "あいうえお".end_with? "お"
  end


  # String#slice!で負の数を指定したら例外ではなくてnilを返すようにした。slice同様に。
  expect nil do
    "abc".slice!(-999)
  end

  # String#index, rindexでto_strを使うようになった。
  expect 3 do 
    obj = Object.new
    def obj.to_str() "y" end
    "ruby".index(obj)
  end
  expect 3 do 
    obj = Object.new
    def obj.to_str() "y" end
    "ruby".rindex(obj)
  end
  
  # String#bytesizeは文字列のバイト数を返す。Ruby 1.8.7ではString#lengthの別名。
  # Ruby 1.9のString#lengthは文字数を返すための移行措置。
  expect 4 do
    "hoge".bytesize
  end


  # Process.execはexecと同じ?

  # Kernel#loopにてStopIteration例外を発生させるとループから抜ける。
  expect :exit_from_loop do
    loop do
      raise StopIteration
    end
    :exit_from_loop
  end

  # Enumerable::Enumerator#nextは終端でStopIteration例外が発生する
  expect StopIteration do
    g = [1].each
    g.next
    g.next
  end
  # だからEnumerable::Enumerator#nextとKernel#loopは組み合わせて使える
  expect :exit_from_loop do
    g = [1].each
    loop do
      g.next
    end
    :exit_from_loop
  end

  # Range#stepは範囲内の要素を s おきに繰り返す。
  expect ["a", "c", "e"] do
    ("a" .. "f").step(2).to_a
  end

  # Dir#inspectがわかりやすくなった。
  expect "#<Dir:/tmp>" do
    Dir.open("/tmp"){|d| d.inspect}
  end

  # Integer#odd? Integer#even? 偶奇判定。
  expect true do
    1.odd?
  end
  expect false do
    1.even?
  end
  expect false do
    12.odd?
  end
  expect true do
    12.even?
  end

  # Integer#predは前の数を返す。
  expect -1 do # !> ambiguous first argument; put parentheses or even spaces
    0.pred
  end
  expect 10 do
    11.pred
  end


  # Integer#ordは文字コード。Ruby 1.9との互換性のため。
  expect 97 do
    97.ord
  end
  expect 97 do
    ?a.ord
  end

  # Hash.[]がto_hashを使うようになった。
  expect({1=>2}) do
    obj = Object.new
    def obj.to_hash() {1=>2} end
    Hash[obj]
  end

  # 0の累乗
  expect(1.0) do
    0**0.0
  end
  expect Rational do
    0**-1
  end
  expect "Rational(1, 0)" do
    (0**-1).inspect
  end

  # shellwords.rbのメソッド群。
  require 'shellwords'
  expect "a\\\\x" do
    'a\\x'.shellescape
  end
  expect ["ruby", "-e", "print 1"] do
    %!ruby -e 'print 1'!.shellsplit
  end
  expect "ruby -e print\\ 1" do
    ["ruby", "-e", "print 1"].shelljoin
  end


  # Tempfile.openで拡張子を指定できるようになった。やったー
  require 'tempfile'
  expect(/\.rb$/) do
    t = Tempfile.open(["temp", ".rb"])
    t.path
  end

  require 'tmpdir'
  # Dir.mktmpdirは一時ディレクトリを作成する。
  expect(/\/foo/) do
    begin
      d = Dir.mktmpdir "foo"
    ensure
      Dir.rmdir d
    end
  end
  expect(/\/foo.*bar$/) do
    begin
      d = Dir.mktmpdir ["foo", "bar"]
    ensure
      Dir.rmdir d
    end
  end

  # Dir#each / Dir.foreach にブロックをつけないとEnumeratorを返す。
  require 'tmpdir'
  expect Enumerable::Enumerator do
    Dir.open(Dir.tmpdir){|d| d.each.class }
  end

  expect Enumerable::Enumerator do
    Dir.foreach(Dir.tmpdir).class
  end

  # ObjectSpace.each_objectにブロックをつけないとEnumeratorを返す。
  expect Enumerable::Enumerator do
    ObjectSpace.each_object.class
  end

  # Regexp.unionの引数に array of String も受け付けるようになった。
  expect(/a|b/) do
    Regexp.union(["a","b"])
  end

  # 安全な乱数発生器
  # require 'securerandom'
  # SecureRandom.hex(10)            # ランダムな16進文字列
  # SecureRandom.base64(10)         # ランダムなbase64文字列
  # SecureRandom.random_bytes(10)   # ランダムなバイナリ文字列

  

end
# >> Expectations ...................................................................................................................
# >> Finished in 0.01327 seconds
# >> 
# >> Success: 115 fulfilled

追記

すんません、まだmap.with_indexは使えませんでした。だからwith_indexのサンプルを変えときました。なんという孔明…

追記

指摘thx。

追記

id:yatmsuさんははてブコメントで「正直、1.8.xには入れて欲しく無かったかなあ。個人的には。」と言っているが、理由を知りたい。数年以内に訪れるであろうRuby 1.9時代に備えての移行措置としてはRuby 1.8.7がいいタイミングだと俺は思うのだが。今のうちにRuby 1.9のメソッドを導入することで移行時の手間を省くことができる。

特にString#linesの導入は大事だと思う。それがないと文字列を行の配列に変換する手軽でポータブルな方法がなくて困る羽目になる。Ruby 1.9の文字列はもはやEnumerableではないのだ。だったら新しいスクリプトを書くときはstr.lines.mapなどと書けるようになるべき。Ruby 1.9でstr.mapと書けなくてギャーってなる前に、str.lines.mapと書くクセをつけることができる。

他のメソッドは…せっかく作ったんだから勢いで入れちまおうってノリかもしれない。便利なメソッドは多いにこしたことはない。

人間だっていきなり大人になるわけではないし、どうしても背伸びをしたくなる思春期がある。Rubyに思春期があってもいいじゃないか。

追記

UnboundMethod#name, UnboundMethod#owner, Dir#each, Dir.foreach, ObjectSpace.each_object, Regexp#unionについて追記。

追記

String#splitだと改行が取り除かれる。こんな感じで微妙に挙動が違う。

s = "a\nb\nc"
s.lines.to_a     # => ["a\n", "b\n", "c"]
s.each_line.to_a # => ["a\n", "b\n", "c"]
s.split(/\n/)    # => ["a", "b", "c"]
s.split(/(\n)/)  # => ["a", "\n", "b", "\n", "c"]

追記

春思う
一八七は
いい花だ

うまい!

追記

5/15 ブロックつきArray#nitemsは削除された。

[2008/05/26]追記

Object#instance_execのテストケースを追加。

[2008/06/01]追記

  • Enumerable#cycleの引数について。
  • String#bytesizeについて。
  • Enumerable::Enumerator#nextとKernel#loopについて。
  • SecureRandomについて。
  • Module#module_execについて。