知って得する21のRubyのトリビアな記法 21 Trivia Notations you should know in Ruby
(追記:2013-08-16) 本記事のトリビアを含む55のトリビアを以下の記事にまとめました。
ちょっとトリビアだけど、知っていると意外と便利なRubyの記法を21個拾ってみたよ(Ruby1.9限定)。
君なら全部知ってるかもしれないけど..
1. 動的継承
Rubyのクラス継承では < 記号の右辺にクラス定数だけでなくクラスを返す式が書けるよ。
class Male
def laugh; 'Ha ha ha!' end
end
class Female
def laugh; 'Fu fu fu..' end
end
class Me < [Male, Female][rand 2]
end
Me.superclass # => Female
Me.new.laugh # => 'Fu fu fu..'
def io(env=:development)
env==:test ? StringIO : IO
end
env = :test
class MyIO < io(env)
end
MyIO.superclass #=> StringIO
つまりRubyでは条件に応じて継承するクラスを動的に変えることができるんだよ。
2. 大文字メソッド
Rubyでは通常メソッド名には英小文字を使うけど、英大文字も許容されてるんだよ。大文字メソッドは一見定数に見えるよね。
class Google
def URL
'www.google.com'
end
private :URL
def search(word)
get( URL(), word)
end
end
定数は継承サブクラスで参照されるけど、これを非公開にしたいこともあるよね。そんなときには大文字メソッドがいいかもね。
引数がないときでもカッコを省略できないという欠点があるけど、関連する複数の定数を定義するときなんかも便利に使えるよ。
class Google
def search(word, code=:us)
get( URL(code), word )
end
def URL(code)
{ us: 'www.google.com',
ja: 'www.google.co.jp' }[code]
end
private :URL
僕は「定数メソッド」って呼んでるんだけど、どうかな?
3. メソッド引数のスペース
Rubyで引数付きメソッドを呼ぶときそのカッコを省略できるけど、引数がシンボルであればさらに、メソッド名との間のスペースも省略できるよ。
def name(sym)
@name = sym
end
name:charlie # => :charlie
こうするとより宣言的に見えるよね。
また* &の後ろのスペースは無視されるから、次のような書き方ができるよ。
def teach_me(question, * args, & block)
google(question, * args, & block)
end
a, b, * c = 1,2,3,4
c # => [3,4]
だからどうしたって話だけど…
4. 関数部分適用
似たようなメソッドを複数書くことはDRY原則に反するよね。Proc#curryを使えばこれを回避できるかもね。四季判定関数の例を示すね。
require "date"
season = ->range,date{ range.include? Date.parse(date).mon }.curry
is_spring = season[4..6]
is_summer = season[7..9]
is_autumn = season[10..12]
is_winter = season[1..3]
is_autumn['11/23'] # => true
is_summer['1/1'] # => false
こうなると変数名に ? が使えるとうれしいんだけどなあ。
5. Procによるcase判定
Procの実行はcallメソッドを呼ぶことで実現できるけど、Proc#===はその別名になってるんだよ。先の四季判定関数をcase式で使う例で使い方を見るね。
for date in %w(2/4 11/23 6/14 8/3)
act =
case date
when is_spring; 'Wake up!'
when is_summer; 'Cool down!'
when is_autumn; 'Read!'
when is_winter; 'Sleep!'
end
puts "#{date} => #{act}"
end
# >> 2/4 => Sleep!
# >> 11/23 => Read!
# >> 6/14 => Wake up!
# >> 8/3 => Cool down!
引数の受け渡しが暗黙的に行われるので、case式が非常にすっきりするよね。
6. Structクラス
属性主体のクラスを生成するときにはStructが便利だよね。
module Fortune
class Teller
require "date"
def self.ask(name, age, occupation)
Date.today.next_day(rand 10)
end
end
end
class Person < Struct.new(:name, :age, :occupation)
def length_of_life(date)
(Fortune::Teller.ask(name, age, occupation) - Date.parse(date)).to_i
end
end
charlie = Person.new('charlie', 13, :programmer)
charlie.length_of_life('2011/6/22') # => 3
実はStruct.newはブロックを取れるから、下のような書き方もできるんだよ。
Person = Struct.new(:name, :age, :occupation) do
def length_of_life(date)
(Fortune::Teller.ask(name, age, occupation) - Date.parse(date)).to_i
end
end
charlie = Person.new('charlie', 13, :programmer)
charlie.length_of_life('2011/6/22') # => 3
7. retryと引数デフォルト
rescue節ではretryを使うことによって、そのブロックの処理を再実行させることができるよね。これをメソッド引数のデフォルト値と組み合わせることで、便利に使えるときがあるんだ。
require "date"
def last_date(date, last=[28,29,30,31])
d = Date.parse date
Date.new(d.year, d.mon, last.pop).day rescue retry
end
last_date '2010/6/1' # => 30
last_date '2010/2/20' # => 28
last_date '2008/2' # => 29
この例では31日からDateオブジェクトの生成を試して、例外が発生するとretryにより次の日付を試していく。
まあ上のはこれでいいんだけど…
Date.new(2009,2,-1).day # => 28
8. 否定
否定に使われる ! あるいは not が好きじゃない人いる?ならBasicObject#!があるよ!
true.! # => false
false.! # => true
1.! # => false
'hello'.!.! # => true
…
次に行きます..
9. %ノーテーション
String#%を使うことで文字列に指定フォーマットでオブジェクトを埋め込めるけど、%は配列を受け取れるんだ。
lang = [:ruby, :java]
"I love %s, not %s" % lang # => "I love ruby, not java"
それだけじゃなくて実はハッシュも取れるんだよ。
lang = {a: :java, b: :ruby}
"I love %{b}, not %{a}" % lang # => "I love ruby, not java"
10. 文字列区切り
文字列を各文字に区切るには、String#splitかString#charsが使えるよね。
alpha = "abcdefghijklmnopqrstuvwxyz"
alpha.split(//) # => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
alpha.chars.to_a # => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
でも文字列を複数文字単位で区切るにはString#scanが便利だよ。
alpha.scan(/.../) # => ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx"]
alpha.scan(/.{1,3}/) # => ["abc", "def", "ghi", "jkl", "mno", "pqr", "stu", "vwx", "yz"]
number = '12345678'
def number.comma_value
reverse.scan(/.{1,3}/).join(',').reverse
end
number.comma_value # => "12,345,678"
11. Array#*
Array#*に整数を渡すとそれを繰り返した新たな配列を返すけど、文字列を渡すとそれをセパレータとした連結文字列を返すjoinの役割を果たすよ。
[1, 2, 3] * 3 # => [1, 2, 3, 1, 2, 3, 1, 2, 3]
[2009, 1, 10] * '-' # => "2009-1-10"
12. Arrayスタック系メソッド
Array#«は一つのオブジェクトしか引数に取れないんだけど、Array#pushは複数取れるんだ。またArray#popは一度に複数の値をポップできる。Array#unshift Array#shiftも同じだよ。
stack = []
stack.push 1, 2, 3 # => [1, 2, 3]
stack.pop 2 # => [2, 3]
stack # => [1]
stack.unshift 4, 5, 6 # => [4, 5, 6, 1]
stack.shift 3 # => [4, 5, 6]
stack # => [1]
また任意位置の複数の値を取り出す場合は、Array#values_atが便利だよ。
lang = %w(ruby python perl haskell lisp scala)
lang.values_at 0, 2, 5 # => ["ruby", "perl", "scala"]
13. Array#uniq
配列から重複した値を取り除くときはArray#uniqを使うけど、uniqはブロックを取れるからそこで重複の条件を指定できるんだ。
Designer = Struct.new(:name, :lang)
data = {'matz' => :ruby, 'kay' => :smalltalk, 'gosling' => :java, 'dhh' => :ruby}
designers = data.to_a.map { |name, lang| Designer[name, lang] }
designers.uniq.map(&:name) # => ["matz", "kay", "gosling", "dhh"]
designers.uniq{ |d| d.lang }.map(&:name) # => ["matz", "kay", "gosling"]
14. Kernel#Array
異なる型の引数を統一的に処理するときにはKernel#Arrayが便利だよ。
Array 1 # => [1]
Array [1,2] # => [1, 2]
Array 1..5 # => [1, 2, 3, 4, 5]
require "date"
def int2month(nums)
Array(nums).map { |n| Date.new(2010,n).strftime "%B" }
end
int2month(3) # => ["March"]
int2month([2,6,9]) # => ["February", "June", "September"]
int2month(4..8) # => ["April", "May", "June", "July", "August"]
15. 文字列リスト%w
文字列のリストを作るときには%wリテラルが便利だけど、文字列が空白文字を含むときはバックスラッシュでエスケープすればいいよ。
designers = %w(John\ McCarthy Yukihiro\ Matsumoto Larry\ Wall Alan\ Kay Martin\ Odersky)
designers # => ["John McCarthy", "Yukihiro Matsumoto", "Larry Wall", "Alan Kay", "Martin Odersky"]
16. 要素区切りコンマ
配列とハッシュの各要素の区切りにはコンマが使われるけど、最後の要素のカンマは無視されるんだよ。
p designers = [
"John McCarthy",
"Yukihiro Matsumoto",
"Larry Wall",
"Alan Kay",
"Martin Odersky",
]
# >> ["John McCarthy", "Yukihiro Matsumoto", "Larry Wall", "Alan Kay", "Martin Odersky"]
p designers = {
:lisp => "John McCarthy",
:ruby => "Yukihiro Matsumoto",
:perl => "Larry Wall",
:smalltalk => "Alan Kay",
:scala => "Martin Odersky",
}
# >> {:lisp=>"John McCarthy", :ruby=>"Yukihiro Matsumoto", :perl=>"Larry Wall", :smalltalk=>"Alan Kay", :scala=>"Martin Odersky"}
要素を頻繁に追加・削除したり、ファイルからevalするときなどにいいかもね。
17. ハッシュリテラル
Ruby1.9ではハッシュの新しい記法が導入されたけど、これは古い記法と混在できるんだ。
designers1 = {
:lisp => "John McCarthy",
:ruby => "Yukihiro Matsumoto",
:perl => "Larry Wall",
:smalltalk => "Alan Kay",
:'C++' => "Bjarne Stroustrup",
}
designers2 = {
java: "James Gosling",
python: "Guido van Rossum",
javascript: "Brendan Eich",
scala: "Martin Odersky",
}
designers = designers1.merge designers2
# => {:lisp=>"John McCarthy", :ruby=>"Yukihiro Matsumoto", :perl=>"Larry Wall", :smalltalk=>"Alan Kay", :"C++"=>"Bjarne Stroustrup", :java=>"James Gosling", :python=>"Guido van Rossum", :javascript=>"Brendan Eich", :scala=>"Martin Odersky"}
18. Enumerable#each_with_object
Enumerable#injectは便利なメソッドだけど、ブロック内で条件指定をするような場合でも各イテレーションで畳込みオブジェクトが返されることを保証しなければならないよ。
designers.inject([]) { |mem, (lang, name)| mem << [name,lang]*'/' if lang[/l/]; mem }
# => ["John McCarthy/lisp", "Larry Wall/perl", "Alan Kay/smalltalk", "Martin Odersky/scala"]
ブロックの最後の「; mem」の部分だよ。
Enumerable#each_with_objectならその手間は要らないよ。
designers.each_with_object([]) { |(lang, name), mem| mem << [name,lang]*'/' if lang[/l/] }
# => ["John McCarthy/lisp", "Larry Wall/perl", "Alan Kay/smalltalk", "Martin Odersky/scala"]
名前が長いからどうしても避けちゃうけどね..reduceにマッピングしてくれたらうれしいなあ。
19. Kernel#loop
無限の繰り返しはコードのブロックをKernel#loopに渡すことで実現できるよね。
require "mathn"
prime = Prime.each
n = 0
loop do
printf "%d " % prime.next
break if n > 10
n += 1
end
# >> 2 3 5 7 11 13 17 19 23 29 31 37
ここでloopにブロックを渡さないとEnumeratorが返るんだよ。これを利用すればloopのインデックスを作ることができるよ1。
loop # => #<Enumerator: main:loop>
loop.with_index do |_,n|
printf "%d " % prime.next
break if n > 10
end
# >> 2 3 5 7 11 13 17 19 23 29 31 37
ブロックの第1引数がnilになっちゃうけど..
20. splat展開
Rubyでアルファベットの配列を作るときなどは通常、以下のようにするよね。
(1..20).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
('a'..'z').to_a # => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z"]
(1..10).to_a + (20..30).to_a # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
これは*(splat)展開を使って以下のようにも書けるよ。
[*1..20] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
[*'a'..'m'] # => ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m"]
[*1..10, *20..30] # => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30]
21. 前置コロン
文字列をシンボルに変換するときは通常、String#internかString#to_symを使うけど、文字列リテラルにコロンを前置することでも可能だよ。
'goodbye'.intern # => :goodbye
'goodbye'.to_sym # => :goodbye
:'goodbye' # => :goodbye
a = 'goodbye'
:"#{a}" # => :goodbye
長かったけどこれで説明を終わるよ。知らないものいくつあった?
よかったら僕にも君のトリビア教えてね!
第2弾を書いたよ! 第2弾!知って得する12のRubyのトリビアな記法 ~ 12 Trivia Notations you should know in Ruby
(追記:2011-6-26) 21の「文字列にコロンを」を「文字列リテラルにコロンを」に変更しました。 (追記:2011-6-27) 2の「カッコを省略できないという欠点があるけど」を「引数がないときでもカッコを省略できないという欠点があるけど」に変更しました。
(comment)
> カッコを省略できないという欠点があるけど
省略できます
> :”aaa” は別のリテラル
»ujihisaさん
コメントありがとう。でも僕の1.9.2p180環境だとuninitialized constantとなってしまうんです。最新版が必要なのかな
»sora_hくん
コメントありがとう
それは別のシンボルという意味ですか?
でも:hello.equal? :’hello’ #=> true になるよ
“aaa bbb”のシンボルを作りたい時に:”aaa bbb”することができるようになっている. 文字列を作る “” リテラルの手前に : をつけるとsymbolのリテラルにはなるが,文字列のオブジェクトの手前に:をつけてもならないよね?
:”“という別のリテラルがある.リテラルは “String” や 1 (数値)や ‘String’ や [Array] などの事ね. >sora_hくん
なるほど確かにそうですね。記述を少し直しました。ありがとう。 >ujihisaさん
あーやっとわかりました。係り受けが曖昧でしたね。記述を直しました。
-> も curry もその存在を知らずに読んでちょっとビックリ。
->range,date{…}.curry より
->range{->date{…}} がわりやすいかなと思いました >s-:さん
コメントありがとう。あーこの例だとそれでもよいですね。ただcurryは動的に引数の数を変えられるのがいいんですよね- :)
- @no6v1さんに教えていただきました. http://friendfeed.com/no6v1/0d7a24e4/loop-with_index-_-i-break-if-p-3-qt-merborne ↩
blog comments powered by Disqus