diff options
Diffstat (limited to 'vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in')
| -rw-r--r-- | vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html new file mode 100644 index 00000000..fcd653e2 --- /dev/null +++ b/vhosts/blog/public/posts/2021-10-02/ruby-then-keyword-and-case-in/index.html @@ -0,0 +1,290 @@ +<!DOCTYPE html> +<html lang="ja-JP"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <meta name="author" content="nsfisis"> + <meta name="copyright" content="© 2021 nsfisis"> + <meta name="description" content="Ruby 3.0 で追加される case in 構文と、then キーワードについて。"> + <meta name="keywords" content="Ruby,Ruby 3"> + <link rel="icon" type="image/svg+xml" href="/favicon.svg"> + <title>【Ruby】 then キーワードと case in | REPL: Rest-Eat-Program Loop</title> + <link rel="stylesheet" href="/style.css?h=37fff6a2f0eef473abde58e55f28ea69"> + <link rel="stylesheet" href="/hl.css?h=340e65ffd5c17713efc9107c06304f7b"> + </head> + <body class="single"> + <header class="header"> + <nav class="nav"> + <ul> + <li> + <a href="/">REPL: Rest-Eat-Program Loop</a> + </li> + <li> + <a href="/about/">About</a> + </li> + <li> + <a href="/posts/">Posts</a> + </li> + <li> + <a href="/slides/">Slides</a> + </li> + <li> + <a href="/tags/">Tags</a> + </li> + </ul> + </nav> + </header> + <main class="main"> + <article class="post-single"> + <header class="post-header"> + <h1 class="post-title">【Ruby】 then キーワードと case in</h1> + <ul class="post-tags"> + <li class="tag"> + <a href="/tags/ruby/">Ruby</a> + </li> + <li class="tag"> + <a href="/tags/ruby3/">Ruby 3</a> + </li> + </ul> + </header> + <div class="post-content"> + <section> + <h2 id="changelog">更新履歴</h2> + <ol> + <li class="revision"> + <time datetime="2021-10-02">2021-10-02</time>: Qiita から移植 + </li> + </ol> + </section> + <div class="admonition"> + <div class="admonition-label"> + NOTE + </div> + <div class="admonition-content"> + この記事は Qiita から移植してきたものです。 元 URL: <a href="https://qiita.com/nsfisis/items/787a8cf888a304497223">https://qiita.com/nsfisis/items/787a8cf888a304497223</a> + </div> + </div> + + <section id="section--tl-dr"> + <h2><a href="#section--tl-dr">TL; DR</a></h2> + <p> + <code>case</code> - <code>in</code> によるパターンマッチング構文でも、<code>case</code> - <code>when</code> と同じように <code>then</code> が使える (場合によっては使う必要がある)。 + </p> + </section> + + <section id="section--what-is-then-keyword"> + <h2><a href="#section--what-is-then-keyword"><code>then</code> とは</a></h2> + <p> + 使われることは稀だが、Ruby では <code>then</code> がキーワードになっている。次のように使う: + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-keyword">if</span> cond <span class="hljs-keyword">then</span> + puts <span class="hljs-string">"Y"</span> +<span class="hljs-keyword">else</span> + puts <span class="hljs-string">"N"</span> +<span class="hljs-keyword">end</span></code></pre> + + <p> + このキーワードが現れうる場所はいくつかあり、<code>if</code>、<code>unless</code>、<code>rescue</code>、<code>case</code> 構文がそれに当たる。 上記のように、何か条件を書いた後 <code>then</code> を置き、式がそこで終了していることを示すマーカーとして機能する。 + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-comment"># Example:</span> + +<span class="hljs-keyword">if</span> x <span class="hljs-keyword">then</span> + a +<span class="hljs-keyword">end</span> + +<span class="hljs-keyword">unless</span> x <span class="hljs-keyword">then</span> + a +<span class="hljs-keyword">end</span> + +<span class="hljs-keyword">begin</span> + a +<span class="hljs-keyword">rescue</span> <span class="hljs-keyword">then</span> + b +<span class="hljs-keyword">end</span> + +<span class="hljs-keyword">case</span> x +<span class="hljs-keyword">when</span> p <span class="hljs-keyword">then</span> + a +<span class="hljs-keyword">end</span></code></pre> + </section> + + <section id="section--why-then-is-usually-unnecessary"> + <h2><a href="#section--why-then-is-usually-unnecessary">なぜ普段は書かなくてもよいのか</a></h2> + <p> + 普通 Ruby のコードで <code>then</code> を書くことはない。なぜか。次のコードを実行してみるとわかる。 + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-keyword">if</span> <span class="hljs-literal">true</span> puts <span class="hljs-string">'Hello, World!'</span> <span class="hljs-keyword">end</span></code></pre> + + <p> + 次のような構文エラーが出力される。 + </p> + + <pre class="highlight monospaced"><code>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</code></pre> + + <p> + 二つ目のメッセージは無視して一つ目を読むと、<code>then</code> か <code>;</code> か改行が来るはずのところ変数だかメソッドだかが現れたことによりエラーとなっているようだ。 + </p> + + <p> + ポイントは改行が <code>then</code> (や <code>;</code>) の代わりとなることである。<code>true</code> の後に改行を入れてみる。 + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-keyword">if</span> <span class="hljs-literal">true</span> +puts <span class="hljs-string">'Hello, World!'</span> <span class="hljs-keyword">end</span></code></pre> + + <p> + 無事 Hello, World! と出力されるようになった。 + </p> + </section> + + <section id="section--why-then-or-linebreak-is-needed"> + <h2><a href="#section--why-then-or-linebreak-is-needed">なぜ <code>then</code> や <code>;</code> や改行が必要か</a></h2> + <p> + なぜ <code>then</code> や <code>;</code> や改行 (以下 「<code>then</code> 等」) が必要なのだろうか。次の例を見てほしい: + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-keyword">if</span> a b <span class="hljs-keyword">end</span></code></pre> + + <p> + <code>then</code> も <code>;</code> も改行もないのでエラーになるが、これは条件式がどこまで続いているのかわからないためだ。 この例は二通りに解釈できる。 + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-comment"># a という変数かメソッドの評価結果が truthy なら b という変数かメソッドを評価</span> +<span class="hljs-keyword">if</span> a <span class="hljs-keyword">then</span> +b +<span class="hljs-keyword">end</span></code></pre> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-comment"># a というメソッドに b という変数かメソッドの評価結果を渡して呼び出し、</span> +<span class="hljs-comment"># その結果が truthy なら何もしない</span> +<span class="hljs-keyword">if</span> a(b) <span class="hljs-keyword">then</span> +<span class="hljs-keyword">end</span></code></pre> + + <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="section--then-in-case-in"> + <h2><a href="#section--then-in-case-in"><code>case</code> - <code>in</code> における <code>then</code></a></h2> + <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> + + <pre class="highlight" language="yacc" linenumbering="unnumbered"><code>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)) %*/ +} +;</code></pre> + + <p> + 簡略版: + </p> + + <pre class="highlight" language="yacc" linenumbering="unnumbered"><code>p_case_body : keyword_in p_top_expr then compstmt p_cases +;</code></pre> + + <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> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-keyword">case</span> x +<span class="hljs-keyword">in</span> <span class="hljs-number">1</span> <span class="hljs-keyword">then</span> a +<span class="hljs-keyword">in</span> <span class="hljs-number">2</span> <span class="hljs-keyword">then</span> b +<span class="hljs-keyword">in</span> <span class="hljs-number">3</span> <span class="hljs-keyword">then</span> c +<span class="hljs-keyword">end</span> + +<span class="hljs-keyword">case</span> x +<span class="hljs-keyword">in</span> <span class="hljs-number">1</span> + a +<span class="hljs-keyword">in</span> <span class="hljs-number">2</span> + b +<span class="hljs-keyword">in</span> <span class="hljs-number">3</span> + c +<span class="hljs-keyword">end</span> + +<span class="hljs-keyword">case</span> x +<span class="hljs-keyword">in</span> <span class="hljs-number">1</span>; a +<span class="hljs-keyword">in</span> <span class="hljs-number">2</span>; b +<span class="hljs-keyword">in</span> <span class="hljs-number">3</span>; c +<span class="hljs-keyword">end</span></code></pre> + + <p> + ところで、<code>p_top_expr</code> には <code>if</code> による guard clause が書けるので、その場合は <code>if</code> - <code>then</code> と似たような見た目になる。 + </p> + + <pre class="highlight" language="ruby" linenumbering="unnumbered"><code class="highlight"><span class="hljs-keyword">case</span> x +<span class="hljs-keyword">in</span> <span class="hljs-number">0</span> <span class="hljs-keyword">then</span> a +<span class="hljs-keyword">in</span> n <span class="hljs-keyword">if</span> n < <span class="hljs-number">0</span> <span class="hljs-keyword">then</span> b +<span class="hljs-keyword">in</span> n <span class="hljs-keyword">then</span> c +<span class="hljs-keyword">end</span></code></pre> + </section> + + <section id="section--outro"> + <h2><a href="#section--outro">まとめ</a></h2> + <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> + </div> + </article> + </main> + <footer class="footer"> + © 2021 nsfisis + </footer> + </body> +</html> |
