LL脳がscalaの勉強を始めたよ その9


Scalaコップ本の5章の3節〜5節をやりますよー、今回は演算子ですよー

久しぶりにパンプキン・シザーズを読みふけってたら5章全部終われなかったです。そんな予想通りであれな感じですが亀の速度で進んでいきますよー

演算子はメソッド

最初のころから散々繰り返されているScala演算子はメソッドだということの総括です

Scalaでの演算子は内部的にメソッドに変換されて処理されますよー、例えばこんなふうに

// 普通の+演算子は
scala> val sum = 1 + 2
sum: Int = 3
// Intの+メソッドを使ってるのと同じ(内部的にこんなふうに処理される)
scala> val sum = (1).+(2)
sum: Int = 3

ちなみにIntの+メソッドは異なる型の引数をとる複数の多重定義されたものがあるので、上のようなInt + Intのようなメソッドの他にLong型を引数にとるメソッドがありマス

// IntにLongを加えることでLong型を返す
scala> val lsum = 1 + 2L 
lsum: Long = 3
// 実際はInt型の+メソッドにLong型を渡しております
scala> val lsum = (1).+(2L) 
lsum: Long = 3

Intの+だけでなく、演算子のなかには多重定義モノが結構あったりしますねー。まあメソッドだということを気にしなければ多重定義ってことも気にしなくてヨサゲなんですが、意識しておけばちょっと違うかな、と(`・ω・´)

演算子の種類

演算子の構文的(と言っていいのかしら?)な種類としては下記3つがあるのデス

  • 前置(prefix)演算子
    • - 7のようにオブジェクトの前にメソッドを記述
  • 中置(infix)演算子
    • 3 + 4 のようにオブジェクトと引数の間にメソッドを記述
  • 後置(postfix)演算子
    • 7 toLongのようにオブジェクトの後ろにメソッドを記述

ちなみに前置演算子、後置演算子は単項演算子なので引数を取らないみたいね。引数をとるという表現がただしいかどうかは不明なんだけど…(´・ω・`)

前置演算子

前置演算子は+, -, !, ~の4つだけデス

それと前置演算子は実際は演算子にunary_を追加したものが正しくて、普段使っているのは略記法みたい。例えば-という演算子だとこんな感じ

// 2.0の-メソッドで負数を求めるっていう感じかしらね
scala> -2.0
res80: Double = -2.0
// 実際はunary_-メソッドなんだけども
scala> (2.0).unary_-
res81: Double = -2.0

ちなみに-がメソッドだということは、 -の-は+に転じるってのも(符号を反転するという)メソッドの重ねがけと考えれば納得が行くような気がしますな

// -の-を実行してみた
scala> -(-3)
res110: Int = 3
// 実際はこんな感じでメソッドの重ねがけ 
scala> ((3).unary_-).unary_-
res111: Int = 3
後置演算子

後置演算子は「メソッドの空() は省略できる」、「メソッド前の.は省略可能」というScalaの省略ルールを使って記述されている

例えばtoLowerCaseメソッドを後置演算子としてつかうと下のようになる

// 後置演算子として使用
scala> val s = "HogeHoge"
scala> s toLowerCase
res84: java.lang.String = hogehoge
// 実際はこう処理されている
scala> s.toLowerCase()

// 省略ルールその1: 空()省略するぜ
scala> s.toLowerCase  
// 省略ルールその2: .を省略するぜ
scala> s toLowerCase()

つまりScalaにとってはメソッドの全てが演算子に成り得るんだー!!Ω ΩΩ< な、なんだってー!!という事みたいデス。

どうでもいいけども後置演算子はなんかメール送信しそうな名前だな(´・ω・`)

算術演算子

まあ、よく使うアレなので羅列しますー

  • 加算: +
  • 減算: -
  • 乗算: *
  • 除算: /
  • 剰余: %

ちなみにchar型の計算もできるみたいなんだけど、これは何を基に計算してるんだろ?バイトコード

scala> 'a' - 'A'
res90: Int = 32

実際はtoIntした値で計算してるみたいだな

// 文字aを整数型に変換
scala> 'a' toInt
res96: Int = 97
// 文字Aを整数型に変換
scala> 'A' toInt
res97: Int = 65

何が基準になってるんだろう…後でしらべよう…

ついでにScala浮動小数点での剰余演算子(%)での丸め処理はIEEE754に沿っていないので、IEEE754の剰余をする場合はMath.IEEEremainderを使うみたいね

// (13.0).%(5.0)の代わり… 
scala> Math.IEEEremainder(13.0, 5.0)
res98: Double = -2.0

まあ、IEEE754がいまいちわかってないので、後で読もう…wikipedia:IEEE754

関係演算子

比較演算子と言ってもいいのかな?これもよく使うアレなので羅列ー

  • より大きい: >
  • より小さい:
  • 以上: <=
  • 以下: >=

論理演算子

論理式で使うアレやコレ

  • 否定: !
  • 論理AND: &&
  • 論理OR: ||

思い切り蛇足なんだけども、論理ANDや論理ORでは左辺だけで結果が分かる場合は右辺の評価はされないんだけど、これを短絡(ショートサーキット)と呼ぶらしい。

実際は計算量を減らすために、論理式の頻出(確定)条件を左辺に持っていくとかよくやるなぁ…

ちなみにコップ本のショートサーキットのサンプルがちょっと面白かったので、若干変更して載せてみる。確かにこういう具体例があればイメージしやすいよなぁ…

// 左辺をtrue、右辺をfalseで定義
scala> def left() = {println("left call"); true}  
left: ()Boolean
scala> def right() = {println("right call"); false}
right: ()Boolean
// 論理ANDは両方評価される
scala> left() && right()                           
left call
right call
res101: Boolean = false
// 論理ORは左辺のみ評価される
scala> left() || right()                           
left call
res102: Boolean = true

// 左辺をfalse、右辺をtrueで定義
scala> def left() = {println("left call"); false}  
left: ()Boolean
scala> def right() = {println("right call"); true} 
right: ()Boolean
// 論理ANDは左辺のみ評価される
scala> left() && right()                          
left call
res103: Boolean = false
// 論理ORは両方評価される
scala> left() || right()                          
left call
right call
res104: Boolean = true
いじょうー

ビット演算まで纏め切れなかったので、そこら辺は次回にまわしますー、2進数表現がすっかり抜けているのでたたき直さないとな…