pockestrap

Programmer's memo

学生LT #4 で、Ruby Quiz という LT をした。

タイトルのとおりです。

student-lt.connpass.com

speakerdeck.com

何を話したか

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% %%%%) # => "%"

それぞれ、":""%"が出力されましたね。何故でしょうか?

まず、上の例のコードについて解説します。

????::?:を分解していきましょう。

  1. まず、最初の?のうち2文字は、先程説明していた文字リテラルになります。
  2. その次の?1文字は、条件演算子?になります。
  3. そして、その次の?:は、文字リテラルになります。
  4. そして、その次の:は条件演算子:になります。
  5. 最後の?:は、文字リテラルです。

つまり、これをわかりやすく書くと

"?" ? ":" : ":"

のようになります。

では、次のp(% %s% %%%%)の、% %s% %%%%を分解していきましょう。

  1. まず、最初の% %s%(スペースが入っていることに注意)は、パーセントリテラルです。
  2. このパーセントリテラルは区切り文字が(スペース)になっています。
  3. つまり、これは"%s%"と等しいです。
  4. 次の4つの%~のうち、最初の一文字は%`演算子を表します。
  5. これはsprintfのような書式で文字列をフォーマットする演算子です。
  6. class String (Ruby 2.4.0)
  7. そして、最後の%%%はパーセントリテラルです。
  8. これは、%を区切り文字としたパーセントリテラルで、値は空文字列です。
  9. つまり、""と等しいです。

これをわかりやすく書くと、

"%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

問題のコードではanilが入ることはないので意味がないコードですが、現実世界では多用されるテクニックです。

そして、次の&である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)は、anilであればnilを返し、anilでなければa & bを実行するコードです。

まとめ

このように Ruby ではトリッキーなコードを書くことができて、とても面白いです。 業務のコードでこのようなコードを乱発していたら迷惑かも知れませんが、たまには息抜きに面白いコードを書いてみてはいかがでしょうか?


  1. これが本当に「演算子」なのか私はその定義を知らないのですが、便宜上「演算子」と呼びます。