ruby on REPL: Rest-Eat-Program Loop https://blog.nsfisis.dev/tags/ruby/ Recent content in ruby on REPL: Rest-Eat-Program Loop Hugo -- gohugo.io ja-JP Sat, 02 Oct 2021 09:38:50 +0900 [Ruby] then キーワードと case in https://blog.nsfisis.dev/posts/2021-10-02/ruby-then-keyword-and-case-in/ Sat, 02 Oct 2021 09:38:50 +0900 https://blog.nsfisis.dev/posts/2021-10-02/ruby-then-keyword-and-case-in/ この記事は 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 を直接読めばよい
]]>
[Ruby] 自身を実行している処理系の種類を判定する https://blog.nsfisis.dev/posts/2021-10-02/ruby-detect-running-implementation/ Sat, 02 Oct 2021 09:37:50 +0900 https://blog.nsfisis.dev/posts/2021-10-02/ruby-detect-running-implementation/ この記事は Qiita から移植してきたものです。 元 URL: https://qiita.com/nsfisis/items/74d7ffeeebc51b20d791


Ruby という言語には複数の実装があるが、それらをスクリプト上からどのようにして programmatically に見分ければよいだろうか。

Object クラスに定義されている RUBY_ENGINE という定数がこの用途に使える。

参考: Object::RUBY_ENGINE

上記ページの例から引用する:

$ ruby-1.9.1 -ve 'p RUBY_ENGINE'
ruby 1.9.1p0 (2009-03-04 revision 22762) [x86_64-linux]
"ruby"
$ jruby -ve 'p RUBY_ENGINE'
jruby 1.2.0 (ruby 1.8.6 patchlevel 287) (2009-03-16 rev 9419) [i386-java]
"jruby"

それぞれの処理系がどのような値を返すかだが、stack overflow に良い質問と回答があった。

What values for RUBY_ENGINE correspond to which Ruby implementations? より引用:

RUBY_ENGINE Implementation
<undefined> MRI < 1.9
‘ruby’ MRI >= 1.9 or REE
‘jruby’ JRuby
‘macruby’ MacRuby
‘rbx’ Rubinius
‘maglev’ MagLev
‘ironruby’ IronRuby
‘cardinal’ Cardinal

なお、この質問・回答は 2014年になされたものであり、値は変わっている可能性がある。MRI (aka CRuby) については執筆時現在 (2020/12/8) も 'ruby' が返ってくることを確認済み。

この表にない主要な処理系として、mruby'mruby' を返す。

mruby 該当部分のソース より引用:

/*
 * Ruby engine.
 */
#define MRUBY_RUBY_ENGINE  "mruby"
]]>