Rubyでは再定義できる演算子と再定義できない演算子があります。
再定義できる演算子はメソッドとして実装されているのでオーバーライドできます。
再定義できない演算子は制御構造なのでオーバーライドできません。
参考: https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html
この記事ではand
とor
のような動作をするメソッドを実装してみようと思います。
メソッドとして実装してみる
まずは普通にメソッドとして書いてみます。
module AndOr
refine(Object) do
def and(o)
if self
o
else
self
end
end
def or(o)
if self
self
else
o
end
end
end
end
return unless $0 == __FILE__
require 'test/unit'
class TestAndOr < Test::Unit::TestCase
using AndOr
def test_return_value
assert { (true and nil ) == true .and(nil ) }
assert { (true and false) == true .and(false) }
assert { (true and true ) == true .and(true ) }
assert { (true or nil ) == true .or(nil ) }
assert { (true or false) == true .or(false ) }
assert { (true or true ) == true .or(true ) }
assert { (false and nil ) == false .and(nil ) }
assert { (false and false) == false .and(false) }
assert { (false and true ) == false .and(true ) }
assert { (false or nil ) == false .or(nil ) }
assert { (false or false) == false .or(false ) }
assert { (false or true ) == false .or(true ) }
assert { (nil and nil ) == nil .and(nil ) }
assert { (nil and false) == nil .and(false) }
assert { (nil and true ) == nil .and(true ) }
assert { (nil or nil ) == nil .or(nil ) }
assert { (nil or false) == nil .or(false ) }
assert { (nil or true ) == nil .or(true ) }
end
end
実行します。
% ruby and_or1.rb
Loaded suite and_or1
Started
.
Finished in 0.017493 seconds.
-----------------------------------------------------------------------------------
1 tests, 18 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-----------------------------------------------------------------------------------
57.17 tests/s, 1028.98 assertions/s
はい。
短絡評価
and
やor
は短絡評価です。
and
の説明をるりまから引用
左辺を評価し、結果が偽であった場合はその値(つまり nil か false) を返します。左辺の評価結果が真であった場合には 右辺を評価しその結果を返します。 and は同じ働きをする優先順位の低い演算子です。
https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html#and
or
の説明をるりまから引用
左辺を評価し、結果が真であった場合にはその値を返します。 左辺の評価結果が偽であった場合には右辺を評価し その評価結果を返します。 or は同じ働きをする優先順位の低い演算子です。
https://docs.ruby-lang.org/ja/latest/doc/spec=2foperator.html#or
以下のようなプログラムではraise
が評価されることはないため、例外は発生しません。
true or raise
false and raise
短絡評価のテストを追加してみる
先程の実装に短絡評価のテストを追加してみます。
module AndOr
refine(Object) do
def and(o)
if self
o
else
self
end
end
def or(o)
if self
self
else
o
end
end
end
end
return unless $0 == __FILE__
require 'test/unit'
class TestAndOr < Test::Unit::TestCase
using AndOr
def test_return_value
assert { (true and nil ) == true .and(nil ) }
assert { (true and false) == true .and(false) }
assert { (true and true ) == true .and(true ) }
assert { (true or nil ) == true .or(nil ) }
assert { (true or false) == true .or(false ) }
assert { (true or true ) == true .or(true ) }
assert { (false and nil ) == false .and(nil ) }
assert { (false and false) == false .and(false) }
assert { (false and true ) == false .and(true ) }
assert { (false or nil ) == false .or(nil ) }
assert { (false or false) == false .or(false ) }
assert { (false or true ) == false .or(true ) }
assert { (nil and nil ) == nil .and(nil ) }
assert { (nil and false) == nil .and(false) }
assert { (nil and true ) == nil .and(true ) }
assert { (nil or nil ) == nil .or(nil ) }
assert { (nil or false) == nil .or(false ) }
assert { (nil or true ) == nil .or(true ) }
end
def test_shortcircuit
assert_nothing_raised { (true or NIL() ) == true .or(NIL() ) }
assert_nothing_raised { (true or FALSE()) == true .or(FALSE() ) }
assert_nothing_raised { (true or TRUE() ) == true .or(TRUE() ) }
assert_nothing_raised { (false and NIL() ) == false .and(NIL() ) }
assert_nothing_raised { (false and FALSE()) == false .and(FALSE()) }
assert_nothing_raised { (false and TRUE() ) == false .and(TRUE() ) }
assert_nothing_raised { (nil and NIL() ) == nil .and(NIL() ) }
assert_nothing_raised { (nil and FALSE()) == nil .and(FALSE()) }
assert_nothing_raised { (nil and TRUE() ) == nil .and(TRUE() ) }
end
private
def NIL
raise 'nil should not be called'
end
def TRUE
raise 'true should not be called'
end
def FALSE
raise 'false should not be called'
end
end
はい。
% ruby and_or2.rb
Loaded suite and_or2
Started
.F
===================================================================================
Failure:
Exception raised:
RuntimeError(<nil should not be called>)
test_shortcircuit(TestAndOr)
and_or2.rb:55:in `test_shortcircuit'
52: end
53:
54: def test_shortcircuit
=> 55: assert_nothing_raised { (true or NIL() ) == true .or(NIL() ) }
56: assert_nothing_raised { (true or FALSE()) == true .or(FALSE() ) }
57: assert_nothing_raised { (true or TRUE() ) == true .or(TRUE() ) }
58:
===================================================================================
Finished in 0.032972 seconds.
-----------------------------------------------------------------------------------
2 tests, 19 assertions, 1 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
50% passed
-----------------------------------------------------------------------------------
60.66 tests/s, 576.25 assertions/s
メソッドの引数は正格な評価がされるようで、メソッドの実行前に評価されてしまいます。
ブロックを使って評価されるのを回避する
ブロックを使うと、ブロックを実行するまでブロックの中身が評価されるのを遅延させられます。
module AndOr
refine(Object) do
def and
if self
yield
else
self
end
end
def or
if self
self
else
yield
end
end
end
end
return unless $0 == __FILE__
require 'test/unit'
class TestAndOr < Test::Unit::TestCase
using AndOr
def test_return_value
assert { (true and nil ) == true .and { nil } }
assert { (true and false) == true .and { false } }
assert { (true and true ) == true .and { true } }
assert { (true or nil ) == true .or { nil } }
assert { (true or false) == true .or { false } }
assert { (true or true ) == true .or { true } }
assert { (false and nil ) == false .and { nil } }
assert { (false and false) == false .and { false } }
assert { (false and true ) == false .and { true } }
assert { (false or nil ) == false .or { nil } }
assert { (false or false) == false .or { false } }
assert { (false or true ) == false .or { true } }
assert { (nil and nil ) == nil .and { nil } }
assert { (nil and false) == nil .and { false } }
assert { (nil and true ) == nil .and { true } }
assert { (nil or nil ) == nil .or { nil } }
assert { (nil or false) == nil .or { false } }
assert { (nil or true ) == nil .or { true } }
end
def test_shortcircuit
assert_nothing_raised { (true or NIL() ) == true .or { NIL() } }
assert_nothing_raised { (true or FALSE()) == true .or { FALSE() } }
assert_nothing_raised { (true or TRUE() ) == true .or { TRUE() } }
assert_nothing_raised { (false and NIL() ) == false .and { NIL() } }
assert_nothing_raised { (false and FALSE()) == false .and { FALSE() } }
assert_nothing_raised { (false and TRUE() ) == false .and { TRUE() } }
assert_nothing_raised { (nil and NIL() ) == nil .and { NIL() } }
assert_nothing_raised { (nil and FALSE()) == nil .and { FALSE() } }
assert_nothing_raised { (nil and TRUE() ) == nil .and { TRUE() } }
end
private
def NIL
raise 'nil should not be called'
end
def TRUE
raise 'true should not be called'
end
def FALSE
raise 'false should not be called'
end
end
はい。
% ruby and_or3.rb
Loaded suite and_or3
Started
..
Finished in 0.019147 seconds.
-----------------------------------------------------------------------------------
2 tests, 27 assertions, 0 failures, 0 errors, 0 pendings, 0 omissions, 0 notifications
100% passed
-----------------------------------------------------------------------------------
104.46 tests/s, 1410.14 assertions/s
ブロックは評価のタイミングをずらしたいとき便利
and
/or
の短絡評価のように、特定の条件によって評価したりしなかったりする動作を実装するとき、ブロックを使うと評価のタイミングをずらせて便利です。