きっかけ
Python3 で FizzBuzz を書くときに、次のようにすると、
def fizzbuzz(n):
f = 'Fizz' if n % 3 == 0 else ''
b = 'Buzz' if n % 5 == 0 else ''
fb = f + b
return fb or n
fizzbuzz(3) is fizzbuzz(6) => True
fizzbuzz(5) is fizzbuzz(10) => True
fizzbuzz(15) is fizzbuzz(30) => True
となり、'Fizz' も 'Buzz' も 'FizzBuzz' も同じ文字列オブジェクトを使い回しています。Python の文字列が immutable だから、安全に使い回せるんですね。
これを Ruby でもやってみたいということです。
RubyInstaller の 2.5.1 を使います。
つまずき
Ruby では、現状、文字列が使い回せるのは 文字列のリテラル("literal") に、-"literal"
か "literal".freeze
したときだとされています。+
メソッドで文字列を結合すると、メソッド呼び出しのたびに新たな文字列オブジェクトが作られるため、Python のようにはいきません。
def fizzbuzz(n)
f = (n % 3).zero? ? -"Fizz" : -""
b = (n % 5).zero? ? -"Buzz" : -""
fb = f + b
fb.empty? ? n : fb
end
fizzbuzz(3).equal? fizzbuzz(6) => false
fizzbuzz(5).equal? fizzbuzz(10) => false
fizzbuzz(15).equal? fizzbuzz(30) => false
ひらめき
Ruby のマニュアルには、式展開はリテラルだと書かれています。だとすると、式展開と -@
なり freeze
なりを組み合わせれば、使い回しのきく文字列が合成できるのではないか?
def fizzbuzz(n)
f = (n % 3).zero? ? -"Fizz" : -""
b = (n % 5).zero? ? -"Buzz" : -""
fb = -"#{f}#{b}"
fb.empty? ? n : fb
end
fizzbuzz(3).equal? fizzbuzz(6) => true
fizzbuzz(5).equal? fizzbuzz(10) => true
fizzbuzz(15).equal? fizzbuzz(30) => true
大成功です。こんなこと当たり前で、ただ私が不勉強なだけなのかもしれないのですが、まだ知らない人とこの感動を分かち合いたいと思い、勇気を持って投稿しました。
いろいろ実験していて、「ひょっとしてバグ?」と思った点
-
fb = -"#{f}#{b}"
をfb = "#{f}#{b}".freeze
にすると、文字列が使い回されない。
OMG! なぜ、両者で振る舞いを変えるのでしょう?
-
# frozen_string_literal: true
にすると、文字列が使い回されない。
ますます、OMG! です。初めから文字列リテラルが frozen なんだから、-
が邪魔なのかと思って削ってみたものの変化はありませんでした。
この振る舞いは意図的なものなのでしょうか?
結論的なもの
# frozen_string_literal: false
+ -"literal"
+ 式展開
= 最強