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について。