タイトルのとおりです。
何を話したか
Ruby のちょっと面白い構文について、クイズ形式で紹介をしました。 (時間が限られていたので、実際にクイズをしたわけではないです…)
前に Meguro.rb で話した Rubyがむずかしい話 - Qiita とテーマが近いかも知れません。
今回は地域Ruby勉強会とかではなかったので、Rubyを知らなくてもある程度分かるであろう構成になっています。
それぞれのクイズの解説をします。 Rubyの気持ちになって、出力を考えてください。
Quiz 1
以下の Ruby コードは、それぞれ何を出力するでしょうか?
p(??) p(%_?_)
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
答えは、こうなります。
p(??) # => "?" p(%_?_) # => "?"
両方共、"?"
が出力されましたね。
何故でしょうか?
まず、上の例の??
は、Rubyの文字リテラルです。
Ruby では、?
を使用することで文字リテラルを書くことができます。
?の後に文字を1字指定した場合はその文字を表す文字列を返します。
https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#char
例:
?? == '?' # => true ?a == 'a' # => true
また、2つ目の例は、パーセントリテラルです。Rubyでは、%
を使用して文字列リテラルを書くことが出来ます。
文字列リテラル、コマンド出力、正規表現リテラル、配列式、シンボルでは、 %で始まる形式の記法を用いることができます。 文字列や正規表現では、
"',
/' など(通常のリテラルの区切り文字)を要素 に含めたい場合にバックスラッシュの数をコードから減らす効果があります。https://docs.ruby-lang.org/ja/latest/doc/spec=2fliteral.html#percent
例:
%_?_ == '?' # => true %!?! == '?' # => true
このように、区切り文字には任意の非英数字を使うことが出来ます。
つまり、1
とかa
とかは区切り文字としては使えませんが、:
や@
は使用することが出来ます。
Quiz 2
では、Quiz 1 を踏まえて、以下の Ruby コードはそれぞれ何を出力するでしょうか?
p(????::?:) p(% %s% %%%%)
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
答えは、こうなります。
p(????::?:) # => ":" p(% %s% %%%%) # => "%"
それぞれ、":"
と"%"
が出力されましたね。何故でしょうか?
まず、上の例のコードについて解説します。
????::?:
を分解していきましょう。
- まず、最初の
?
のうち2文字は、先程説明していた文字リテラルになります。 - その次の
?
1文字は、条件演算子の?
になります。 - そして、その次の
?:
は、文字リテラルになります。 - そして、その次の
:
は条件演算子の:
になります。 - 最後の
?:
は、文字リテラルです。
つまり、これをわかりやすく書くと
"?" ? ":" : ":"
のようになります。
では、次のp(% %s% %%%%)
の、% %s% %%%%
を分解していきましょう。
- まず、最初の
% %s%
(スペースが入っていることに注意)は、パーセントリテラルです。 - このパーセントリテラルは区切り文字が
(スペース)になっています。
- つまり、これは
"%s%"
と等しいです。 - 次の4つの
%~のうち、最初の一文字は
%`演算子を表します。 - これは
sprintf
のような書式で文字列をフォーマットする演算子です。 - class String (Ruby 2.4.0)
- そして、最後の
%%%
はパーセントリテラルです。 - これは、
%
を区切り文字としたパーセントリテラルで、値は空文字列です。 - つまり、
""
と等しいです。
これをわかりやすく書くと、
"%s%" % "" # sprintf メソッドを使うと次のようになる sprintf("%s%", "")
となります。
Quiz 3
次の Ruby コードは、何を出力するでしょうか?(わかりやすいように、スライドとは少しコードを変えています)
a = ["1","2","3"] a&.map(&:to_i)&.&([1, 4])
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
答えは、こうなります。
a = ["1","2","3"] a&.map(&:to_i)&.&([1, 4]) # => [1]
このコードには&
が4つ含まれていますが、これらには3つの意味があります。
まず1つ目の&
である、a&.
の&
は Safe Navigation Operator です。
Safe Navigation Operator は Ruby 2.3 から追加された構文で、nil
ガードを行います。
# レシーバが nil 出ない時は、通常のメソッド呼び出しと同じ a = "10" a&.to_i # => 10 # レシーバが nil の時は、メソッド呼び出しが行われない a = nil a&.to_i # => nil
問題のコードではa
にnil
が入ることはないので意味がないコードですが、現実世界では多用されるテクニックです。
そして、次の&
であるmap(&:to_i)
の&
は、暗黙的にto_proc
を呼び出す演算子1です。
メソッド呼び出し(super・ブロック付き・yield) (Ruby 2.4.0)
これは、以下のコードと同様になります。
map { |n| n.to_i }
そして、次の&.&
のうち、最初の&.
は Safe Navigation Operator です。
また、最後の&
は積集合を計算する演算子です。
class Array (Ruby 2.4.0)
Ruby では、(だいたいの)演算子はメソッドです。そのため、演算子をメソッドのように呼び出すことが出来ます。
例:
1 + 1 == 1.+(1) # => true [1,2,3] & [2,3,4] == [1,2,3].&([2,3,4]) # => true
このようにメソッド呼び出しのように演算子を呼び出すことで、Safe Navigation Operator を使用することが出来ます。
つまり、a&.&(b)
は、a
がnil
であればnil
を返し、a
がnil
でなければa & b
を実行するコードです。
まとめ
このように Ruby ではトリッキーなコードを書くことができて、とても面白いです。 業務のコードでこのようなコードを乱発していたら迷惑かも知れませんが、たまには息抜きに面白いコードを書いてみてはいかがでしょうか?