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 <matz@ruby-lang.org> まで 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について。