2011-06-22
■知って得する21のRubyのトリビアな記法 ~ 21 Trivia Notations you should know in Ruby
ちょっとトリビアだけど
知っていると意外と便利な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.inject([]) { |mem, (name, lang)| mem << Designer[name, lang]; mem } 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.update 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
長かったけどこれで説明を終わるよ
知らないものいくつあった?
よかったら僕にも君のトリビア教えてね!
*1:@no6v1さんに教えていただきました. http://friendfeed.com/no6v1/0d7a24e4/loop-with_index-_-i-break-if-p-3-qt-merborne
- 57 http://b.hatena.ne.jp/hotentry/it
- 23 http://reader.livedoor.com/reader/
- 21 http://b.hatena.ne.jp/hotentry
- 21 http://d.hatena.ne.jp/
- 15 http://twitter.com/
- 13 http://twitturls.com/
- 10 http://www.google.co.jp/reader/view/
- 7 http://b.hatena.ne.jp/entrylist
- 7 http://longurl.org
- 6 http://htn.to/sjtNN1