summaryrefslogtreecommitdiffhomepage
path: root/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc
diff options
context:
space:
mode:
Diffstat (limited to 'vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc')
-rw-r--r--vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc266
1 files changed, 266 insertions, 0 deletions
diff --git a/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc
new file mode 100644
index 00000000..994b1d7d
--- /dev/null
+++ b/vhosts/blog/content/posts/2021-10-02/ruby-then-keyword-and-case-in.ndoc
@@ -0,0 +1,266 @@
+---
+[article]
+title = "【Ruby】 then キーワードと case in"
+description = "Ruby 3.0 で追加される case in 構文と、then キーワードについて。"
+tags = [
+ "ruby",
+ "ruby3",
+]
+
+[[article.revisions]]
+date = "2021-10-02"
+remark = "Qiita から移植"
+---
+<article>
+ <note>
+ この記事は Qiita から移植してきたものです。
+ 元 URL: <a href="https://qiita.com/nsfisis/items/787a8cf888a304497223">https://qiita.com/nsfisis/items/787a8cf888a304497223</a>
+ </note>
+ <section id="tl-dr">
+ <h>TL; DR</h>
+ <p>
+ <code>case</code> - <code>in</code> によるパターンマッチング構文でも、<code>case</code> - <code>when</code>
+ と同じように <code>then</code> が使える (場合によっては使う必要がある)。
+ </p>
+ </section>
+ <section id="what-is-then-keyword">
+ <h><code>then</code> とは</h>
+ <p>
+ 使われることは稀だが、Ruby では <code>then</code>
+ がキーワードになっている。次のように使う:
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ if cond then
+ puts "Y"
+ else
+ puts "N"
+ end
+ ]]>
+ </codeblock>
+ <p>
+ このキーワードが現れうる場所はいくつかあり、<code>if</code>、<code>unless</code>、<code>rescue</code>、<code>case</code>
+ 構文がそれに当たる。 上記のように、何か条件を書いた後 <code>then</code>
+ を置き、式がそこで終了していることを示すマーカーとして機能する。
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ # Example:
+
+ if x then
+ a
+ end
+
+ unless x then
+ a
+ end
+
+ begin
+ a
+ rescue then
+ b
+ end
+
+ case x
+ when p then
+ a
+ end
+ ]]>
+ </codeblock>
+ </section>
+ <section id="why-then-is-usually-unnecessary">
+ <h>なぜ普段は書かなくてもよいのか</h>
+ <p>
+ 普通 Ruby のコードで <code>then</code>
+ を書くことはない。なぜか。次のコードを実行してみるとわかる。
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ if true puts 'Hello, World!' end
+ ]]>
+ </codeblock>
+ <p>
+ 次のような構文エラーが出力される。
+ </p>
+ <codeblock>
+ <![CDATA[
+ 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
+ ]]>
+ </codeblock>
+ <p>
+ 二つ目のメッセージは無視して一つ目を読むと、<code>then</code> か <code>;</code>
+ か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。
+ </p>
+ <p>
+ ポイントは改行が <code>then</code> (や <code>;</code>) の代わりとなることである。<code>true</code>
+ の後に改行を入れてみる。
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ if true
+ puts 'Hello, World!' end
+ ]]>
+ </codeblock>
+ <p>
+ 無事 Hello, World! と出力されるようになった。
+ </p>
+ </section>
+ <section id="why-then-or-linebreak-is-needed">
+ <h>なぜ <code>then</code> や <code>;</code> や改行が必要か</h>
+ <p>
+ なぜ <code>then</code> や <code>;</code> や改行 (以下 「<code>then</code> 等」)
+ が必要なのだろうか。次の例を見てほしい:
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ if a b end
+ ]]>
+ </codeblock>
+ <p>
+ <code>then</code> も <code>;</code>
+ も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。
+ この例は二通りに解釈できる。
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ # a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価
+ if a then
+ b
+ end
+ ]]>
+ </codeblock>
+ <codeblock language="ruby">
+ <![CDATA[
+ # a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、
+ # その結果が truthy なら何もしない
+ if a(b) then
+ end
+ ]]>
+ </codeblock>
+ <p>
+ <code>then</code> 等はこの曖昧性を排除するためにあり、条件式は <code>if</code> から <code>then</code>
+ 等までの間にある、ということを明確にする。 C系の <code>if</code> 後に来る <code>(</code>/<code>)</code>
+ や、Python の <code>:</code>、Rust/Go/Swift などの <code>{</code> も同じ役割を持つ。
+ </p>
+ <p>
+ Ruby の場合、プログラマーが書きやすいよう改行でもって <code>then</code>
+ が代用できるので、ほとんどの場合 <code>then</code> は必要ない。
+ </p>
+ </section>
+ <section id="then-in-case-in">
+ <h><code>case</code> - <code>in</code> における <code>then</code></h>
+ <p>
+ ようやく本題にたどり着いた。来る Ruby 3.0 では <code>case</code> と <code>in</code>
+ キーワードを使ったパターンマッチングの構文が入る予定である。この構文でもパターン部との区切りとして
+ <code>then</code> 等が必要になる。 (現在の) Ruby には formal
+ な形式での文法仕様は存在しないので、yacc の定義ファイルを参照した (yacc
+ の説明は省略)。
+ </p>
+ <p>
+ <a href="https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986">https://github.com/ruby/ruby/blob/221ca0f8281d39f0dfdfe13b2448875384bbf735/parse.y#L3961-L3986</a>
+ </p>
+ <codeblock language="yacc">
+ <![CDATA[
+ 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)) %*/
+ }
+ ;
+ ]]>
+ </codeblock>
+ <p>
+ 簡略版:
+ </p>
+ <codeblock language="yacc">
+ <![CDATA[
+ p_case_body : keyword_in p_top_expr then compstmt p_cases
+ ;
+ ]]>
+ </codeblock>
+ <p>
+ ここで、<code>keyword_in</code> は文字通り <code>in</code>、<code>p_top_expr</code>
+ はいわゆるパターン、<code>then</code> は <code>then</code>
+ キーワードのことではなく、この記事で <code>then</code> 等と呼んでいるもの、つまり
+ <code>then</code> キーワード、<code>;</code>、改行のいずれかである。
+ </p>
+ <p>
+ これにより、<code>case</code> - <code>when</code> による従来の構文と同じように、<code>then</code>
+ 等をパターンの後ろに挿入すればよいことがわかった。つまり次の3通りのいずれかになる:
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ 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
+ ]]>
+ </codeblock>
+ <p>
+ ところで、<code>p_top_expr</code> には <code>if</code> による guard clause
+ が書けるので、その場合は <code>if</code> - <code>then</code> と似たような見た目になる。
+ </p>
+ <codeblock language="ruby">
+ <![CDATA[
+ case x
+ in 0 then a
+ in n if n < 0 then b
+ in n then c
+ end
+ ]]>
+ </codeblock>
+ </section>
+ <section id="outro">
+ <h>まとめ</h>
+ <ul>
+ <li>
+ <code>if</code> や <code>case</code> の条件の後ろには <code>then</code>、<code>;</code>、改行のいずれかが必要
+ <ul>
+ <li>通常は改行しておけばよい</li>
+ </ul>
+ </li>
+ <li>3.0 で入る予定の <code>case</code> - <code>in</code> でも <code>then</code> 等が必要になる</li>
+ <li>Ruby の構文を正確に知るには (現状) <code>parse.y</code> を直接読めばよい</li>
+ </ul>
+ </section>
+</article>