LoginSignup
8
4

More than 5 years have passed since last update.

条件式では特殊な動作をするRubyリテラル

Posted at

Rubyにおいて一部のクラスのオブジェクトは、new などを使用せずにリテラルとして直接記述できる。ただし条件式の文脈で書いた場合は、必ずしも通常のオブジェクト扱い(false, nil 以外は真と評価)となるわけではないらしい。

もちろん、変数やメソッドなどを条件式にする分には問題無い。その結果がnilかどうかで条件分岐することはよく行われている。また、リテラルを条件式にすることはふつう無いので、動作を知らなくてもバグを生む可能性は少ない。この記事は注意書きではなく、単に個人的に驚いた仕様を少し触って調べたものである。

警告が出たりするリテラル

確認できた分だけ列挙。

正規表現

マニュアルのif文の説明にさらっと書いてある。組み込み変数 $_Kernel.#getsKernel.#readline で最後に読み込んだ文字列)とのマッチを評価するとのこと。

irb(main):001:0> puts 'true' if /regexp/
(irb):1: warning: regex literal in condition
=> nil
irb(main):002:0> gets   # $_に値を入れるため
geregexpxe
=> "geregexpxe\n"
irb(main):003:0> puts 'true' if /regexp/
(irb):3: warning: regex literal in condition
true
=> nil

範囲式

通常は両端に比較可能なオブジェクトを指定して Range オブジェクトを作るが、「条件式としての範囲式」では両端に条件式を指定する。

irb(main):001:0> puts 'true' if true..false
(irb):1: warning: range literal in condition
true
=> nil

基本的な使い方については、記事「Ruby でフリップフロップ」で詳しく説明されている。実用的なテキスト処理を例にしていて、前節の正規表現リテラルも使われている上に、AWKやPerlの場合も載っていて、これらの機能の価値が分かりやすい。

(文字列)

警告が出るだけで問題は無い?

irb(main):001:0> puts 'true' if 'string'
(irb):1: warning: string literal in condition
true
=> nil

(整数)

「条件式としての範囲式」の中で指定すると警告が出る。true に判定されない理由がよく分からなかった。また、リテラルを括弧で囲むだけで警告はなくなり評価結果が変わる。

irb(main):001:0> puts 'true' if 0..9
(irb):1: warning: integer literal in conditional range
(irb):1: warning: integer literal in conditional range
=> nil
irb(main):002:0> puts 'true' if (0..9)
true
=> nil

条件式として扱われる場所

試した結果からの推測なので、抜け漏れや誤解があるかもしれない。

  • if, while, 条件演算子など
  • not
  • 条件式中のandなど
  • 条件式としての範囲式
irb(main):001:0> puts 'true' while /(?!)/
(irb):1: warning: regex literal in condition
=> nil

irb(main):002:0> puts !/regexp/
(irb):2: warning: regex literal in condition
true
=> nil

irb(main):003:0> puts /a/ && /b/   # これだと単なる正規表現オブジェクト同士の&&
(?-mix:b)
=> nil
irb(main):004:0> puts 'true' if /a/ && /b/   # これなら$_とのマッチ評価の論理積
(irb):4: warning: regex literal in condition
(irb):4: warning: regex literal in condition
=> nil

irb(main):005:0> puts 'true' if /a/../b/
(irb):5: warning: regex literal in condition
(irb):5: warning: regex literal in condition
=> nil

小技?

いま使いたいわけではないが、何かあったときに選択肢のひとつとなるかもしれないので、できそうなことを考えてみる。

評価結果の受け取り

すぐに条件分岐させるのではなく、評価結果を一旦変数に記憶したいことがあるはず。しかしリテラルを普通に代入すればただのオブジェクトになってしまう。

対策は簡単で、if文や条件演算子を使ってtrue/falseを返せば結果を受け取れるし、もっと短くnotを2回かけてもできる。

irb(main):001:0> result = true..false   # これだと条件式にならない
ArgumentError: bad value for range
        from (irb):1
        from C:/Ruby/Ruby-2.1.1/bin/irb.bat:18:in `<main>'
irb(main):002:0> result = if true..false then true else false end
(irb):2: warning: range literal in condition
=> true
irb(main):003:0> result = true..false ? true : false
(irb):3: warning: range literal in condition
=> true
irb(main):004:0> result = !!(true..false)
(irb):4: warning: range literal in condition
=> true

ループ文の条件式で使用

通常はif文の条件式でしか使わなそうだが、whileなどのループ文でも何かできないかなと。

irb(main):001:0> gets; gets until /\Ayes\Z/   # yesと入力するまでループ
(irb):1: warning: regex literal in condition
no
no
yes
=> nil

条件式のネスト

範囲式や論理演算子は入れ子にすることができる。何か面白いことができそうな気はするのだが、評価順序に頭が追いつかなくて全く思いつかない。

irb(main):001:0> (1..10).each { |i| puts ((i%3==0)..(i%2==0))..((i%4==0)..(i%7==0)) ? -i : i }
1
2
-3
-4
-5
-6
7
8
-9
-10
=> 1..10

個々の評価順序と結果は以下の通り。

i i%3==0 i%2==0 i%4==0 i%7==0 結果
1 false false
2 false false
3 true false false true
4 true false true
5 false false true
6 true false true
7 false false
8 false false
9 true false false true
10 true false true
8
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
4