更新履歴

  1. : Qiita から移植

この記事は Qiita から移植してきたものです。 元 URL:https://qiita.com/nsfisis/items/787a8cf888a304497223


TL; DR

case-inによるパターンマッチング構文でも、case-whenと同じようにthenが使える (場合によっては使う必要がある)。

thenとは

使われることは稀だが、Ruby ではthenがキーワードになっている。次のように使う:

if cond then
  puts "Y"
  else
  puts "N"
  end

このキーワードが現れうる場所はいくつかあり、ifunlessrescuecase構文がそれに当たる。 上記のように、何か条件を書いた後thenを置き、式がそこで終了していることを示すマーカーとして機能する。

# Example:

if x then
a
end

unless x then
a
end

begin
a
rescue then
b
end

case x
when p then
a
end

なぜ普段は書かなくてもよいのか

普通 Ruby のコードでthenを書くことはない。なぜか。次のコードを実行してみるとわかる。

if true puts 'Hello, World!' end

次のような構文エラーが出力される。

20:1: syntax error, unexpected local variable or method, expecting `then' or ';' or '\n'
if true puts 'Hello, World!' end
        ^~~~
20:1: syntax error, unexpected `end', expecting end-of-input
...f true puts 'Hello, World!' end

二つ目のメッセージは無視して一つ目を読むと、then;か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。

ポイントは改行がthen(や;) の代わりとなることである。trueの後に改行を入れてみる。

if true
puts 'Hello, World!' end

無事 Hello, World! と出力されるようになった。

なぜthen;や改行が必要か

なぜthen;や改行 (以下 「then等」) が必要なのだろうか。次の例を見てほしい:

if a b end

then;も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 この例は二通りに解釈できる。

# a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
if a then
b
end
# a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
# その結果が truthy なら何もしない
if a(b) then
end

then等はこの曖昧性を排除するためにあり、条件式はifからthen等までの間にある、ということを明確にする。 C系のif後に来る(/)や、Python の:、Rust/Go/Swift などの{も同じ役割を持つ。

Ruby の場合、プログラマーが書きやすいよう改行でもってthenが代用できるので、ほとんどの場合thenは必要ない。

case-inにおけるthen

ようやく本題にたどり着いた。来る Ruby 3.0 ではcaseinキーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとしてthen等が必要になる。 (現在の) Ruby には formal な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc の説明は省略)。

https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986

p_case_body : keyword_in
  {
  SET_LEX_STATE(EXPR_BEG|EXPR_LABEL);
  p->command_start = FALSE;
  $<ctxt>1 = p->ctxt;
  p->ctxt.in_kwarg = 1;
  $<tbl>$ = push_pvtbl(p);
  }
  {
  $<tbl>$ = push_pktbl(p);
  }
  p_top_expr then
  {
  pop_pktbl(p, $<tbl>3);
  pop_pvtbl(p, $<tbl>2);
  p->ctxt.in_kwarg = $<ctxt>1.in_kwarg;
  }
  compstmt
  p_cases
  {
  /*%%%*/
  $$ = NEW_IN($4, $7, $8, &@$);
  /*% %*/
  /*% ripper: in!($4, $7, escape_Qundef($8)) %*/
  }
  ;

簡略版:

p_case_body : keyword_in p_top_expr then compstmt p_cases
;

ここで、keyword_inは文字通りinp_top_exprはいわゆるパターン、thenthenキーワードのことではなく、この記事でthen等と呼んでいるもの、つまりthenキーワード、;、改行のいずれかである。

これにより、case-whenによる従来の構文と同じように、then等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:

case x
in 1 then a
in 2 then b
in 3 then c
end

case x
in 1
a
in 2
b
in 3
c
end

case x
in 1; a
in 2; b
in 3; c
end

ところで、p_top_exprにはifによる guard clause が書けるので、その場合はif-thenと似たような見た目になる。

case x
in 0 then a
in n if n < 0 then b
in n then c
end

まとめ

  • ifcaseの条件の後ろにはthen;、改行のいずれかが必要
    • 通常は改行しておけばよい
  • 3.0 で入る予定のcase-inでもthen等が必要になる
  • Ruby の構文を正確に知るには (現状)parse.yを直接読めばよい