高卒文系Scala vol.13 - 演算子はメソッド -

Scala本、5.3~5.6辺り。演算子の説明ですが、使い方は殆どJavaと一緒です。

Scalaでは、全てのデータがオブジェクトである為、

演算子も基本データ型のインスタンスメソッドとして提供されています。

副作用の無いインスタンスメソッドは演算子と同じとも言えますねー。

メソッド記法と演算子記法

一般的に、副作用のあるメソッドは従来のメソッド記法で、副作用の無いメソッド(関数)は演算子記法で書くらしいです。

val s = "Hello, scalable world."
Predef.println(s substring 7)
Predef.println(s lastIndexOf("l", 15))
scalable world.
13

String#substring」、「String#lastIndexOf」は副作用がないので、「.」(ドット)を使わない演算子記法で 、

Predef.println」は標準出力という副作用がある為、「.」(ドット)を使うメソッド記法で書いてみました。

メソッド記法も、演算子記法も、インスタンスメソッドの呼び出しの記述方法が違うだけなのですが

両方の書き方を適切に使い分ける事で、コードの可読性を高める事ができそうです。

演算子メソッド

それぞれの演算子がどのような処理を行なうかはJavaと同じ、と考えても問題は無さそうです。

Scalaでは「演算子=メソッド」なので、それぞれの演算子がどのメソッド呼び出しであるか確認してみました。

単項演算子
val num = 100
println(num.unary_+) // +num
println(num.unary_-) // -num
println(num.unary_~) // ~num
100
-100
-101

「単項」=「unary」、らしいです。

算術演算子
val positiveNum = 10.0 // Double
val negativeNum = -10  // Int
println(positiveNum.+(negativeNum))   // positiveNum + negativeNum
println(positiveNum.-(negativeNum))   // positiveNum - negativeNum
println(positiveNum.*(negativeNum))   // positiveNum * negativeNum
println(positiveNum./(negativeNum))   // positiveNum / negativeNum
println(positiveNum.%(negativeNum))   // positiveNum % negativeNum
0.0
20.0
-100.0
-1.0
0.0

算術演算子メソッドは、オーバーロードされているので、

引数(右辺)の型が違っていてもJavaのようにわざわざ型変換する必要はありません。

結果型はレシーバー(メソッド呼び出し側:左辺)の型と同じものになります。

比較演算子
val positiveNum = 10.0 // Double
val negativeNum = -10  // Int
println(positiveNum.==(negativeNum))  // positiveNum == negativeNum
println(positiveNum.!=(negativeNum))  // positiveNum != negativeNum
println(positiveNum.<(negativeNum))   // positiveNum < negativeNum
println(positiveNum.<=(negativeNum))  // positiveNum <= negativeNum
println(positiveNum.>(negativeNum))   // positiveNum > negativeNum
println(positiveNum.>=(negativeNum))  // positiveNum >= negativeNum
false
true
false
false
true
true

比較演算子メソッドも、オーバーロードされています。

論理演算子
val flg1 = true;
val flg2 = false;
println(flg1.unary_!)   // !flg1
println(flg1.&(flg2))   // flg1 & flg2
println(flg1.&&(flg2))  // flg1 && flg2
println(flg1.|(flg2))   // flg1 | flg2
println(flg1.||(flg2))  // flg1 || flg2
false
false
false
true
true

「&&」や「||」などのショートサーキットは、「名前渡しパラメータ」で定義されているので意図通りに機能します。

左辺でtrueが返された場合、右辺は評価されません。

ものすっごく簡単に言うと、「&&」と「||」メソッドはBooleanの値を引数に渡しているのではなく、

「Boolean型を返す関数リテラル」を渡しています。

引数に渡した関数が実行されるのはそれぞれのメソッドの中なので、

レシーバーがtrueを返した場合、引数に渡された関数は実行されません。

ビット演算子
val bit1 = 1
val bit2 = 2
println(bit1.&(bit2)) // bit1 & bit2 : AND
println(bit1.|(bit2)) // bit1 | bit2 : OR
println(bit1.^(bit2)) // bit1 ^ bit2 : XOR
println(bit1.unary_~) // ~bit1
03
3
-2

JavaやScalaなど低レイヤを意識する必要が無い言語ではあまり使われないと思いますが、念のため。

これらの演算子メソッドは使う分には、メソッドである事を意識する必要は全くありません。

が、「自分で演算子を定義できる」事ってのは結構重要かと。

使いどころを間違えなければ、物凄く強力だと思います。

C++の演算子オーバーロードみたいなモノですね。

このエントリーをはてなブックマークに追加
comments powered by Disqus