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


Scalaコップ本の11章残りをやってしまいますよー、今回はプリミティブ型なんかについてデス

11章は全体的に座学的な内容なのでほぼ写経…(´・ω・`)…まあ頑張ろうかしらね

プリミティブ型の実装方法

Scalaでの整数演算はJVMとの互換性の点からプリミティブ型の演算として定義されているようデス

ただし、整数をオブジェクトとして扱う必要ができた場合はjava.lang.Integerというバックアップクラスを使うらしいデスね。ちなみに整数をオブジェクトとして扱うのはtoStringメソッドの呼び出しやAny型の変数に整数を代入したりする場合とのことです。

このようにInt型の整数はjava.lang.Integerに透過的に変換されるらしく、この場合のjava.lang.Integerを「ボクシングされた整数」と呼ぶそうです。ちなみに”ボクシングされた”という表現は「値型を参照型に変換された」というものでJava5のオートボクシング(値型と参照型間の自動変換)に似た話らしいデス。

Java5のオートボクシングとの違い

ただし、Java5のオートボクシングでは変換後の型によって扱いが変わってしまうことを実装側で意識しなければならない、という扱いにくい点をScalaでは改善しておりますデス。

例えばJavaでは次の2つの関数で結果が異なってしまうらしいです

// javaコードですよー

// Int型(値型)での等価比較
Boolean isequal(int x, int y){
  return x == y;
}
// 同じ値を比較するので結果はTrue
isEqual(321, 321)

// Integer型(参照型)での等価比較
Boolean isequal(int x, int y){
  return x == y;
}
// ボクシングによって異なるオブジェクトの参照が作られて
// それらの参照等価性によって比較するので結果はFalse
// (異なるオブジェクトなので違うものとして評価されマス)
isEqual(321, 321)

javaではこんな感じでボクシングされることを意識しておかないと、振る舞いが制御できないという自体になってしまうみたいデス。

コップ本的にはこのプリミティブ型と参照型にはっきりとした違いが出てしまうことを「javaが純粋なオブジェクト指向でない側面の一つだ!」(;゚Д゚)(゚Д゚;(゚Д゚;)ナ、ナンダッテー!!って断言してます。まあ、少なくても静的型付けが苦手なLL脳にはハードルが高い現象であることは確かですが(´・ω・`)

でもScalaなら大丈夫(`・ω・´)

// 値型で比較しますよ
scala> def isEqual(x:Int, y:Int) = x == y
isEqual: (Int,Int)Boolean
// 同じ値なのでtrueです
scala> isEqual(321, 321)
res0: Boolean = true

// Anyへの暗黙的変換でボクシングされますので
// 参照型の比較演算ですよ
scala> def isEqual(x:Any, y:Any) = x == y
isEqual: (Any,Any)Boolean
// オブジェクトの参照先の値が等しいのでtrueですよ(って解釈でよいかしら?)
scala> isEqual(321, 321)                 
res1: Boolean = true

ほら、同じ結果でしょ?ちなみにコードもスッキリデス

実際Scalaの参照型での==はオブジェクトから継承されたequalsメソッドの別名として振舞うらしいので、多くのサブクラスでは上のような自然な等価的振る舞いをするようにequalsメソッドをオーバーライドして定義しているよ(゚∀゚)、とのことデス

例えばこんなjavaでの有名な罠(らしいデスネ)もScalaならOKだってさ

// x, yともにオブジェクトに変換されますね
scala> val x = "abcde".substring(3)
x: java.lang.String = de
scala> val y = "abcde".substring(3)
y: java.lang.String = de

// 2つのオブジェクトの参照先の値が等しいのでtrueですな
// でもjavaだとダメらしいですねー
scala> x == y
res2: Boolean = true
ユーザ定義じゃない等価比較をしたい場合は?

ただし== のように各サブクラスでオーバーライドされた(ユーザー定義の)等価比較でなく、純粋な参照等価性的評価をおこないたい場合なんかはAnyRefクラスでfinal定義されたeqを使うといいみたいです。eqはオーバーライドできないので参照等価性が担保されておりますデスよ。ついでに否定の場合のneも同様ですねー

// x, y 2つ参照型を作成しますよ
scala> val x = new String("scala")
x: java.lang.String = scala
scala> val y = new String("scala")
y: java.lang.String = scala

////// 等価比較デス
// 参照先の値を比較しますのでtrueですよ
scala> x == y
res3: Boolean = true
// 参照等価性の比較で、違うオブジェクトの比較なのでfalseですねー
scala> x eq y
res4: Boolean = false

// 参照先の値の非等価比較なのでfalseデス
scala> x != y
res5: Boolean = false
// 参照非等価性の比較で、違うオブジェクトの比較なのでtrueデスねー
scala> x ne y
res6: Boolean = true

Scalaでの等価性概念は28章で詳しくやるみたいですな…このペースだといつになるやら(´・ω・`)

最下位(bottom)の2つの型

コップ本196ページのScala階層構造図の中で最下層に配置されているのがscala.Nullとscala.Nothingクラスみたいデス。こいつらはScala的型システムの中に稀に発生する厄介な条件を扱うための特殊な型だそうデス

scala.Nullクラス

null型参照の型で全ての参照クラス(AnyRefクラスを継承したオブジェクト的クラス?)のサブクラスになるみたいデス。なので値型とは互換性がないため次のようなことをすると怒られますな。

// 値型にnull突っ込むなYO!と怒られましたよ
scala> val i:Int = null
<console>:4: error: type mismatch;
 found   : Null(null)
 required: Int
       val i:Int = null
                   ^

まあ、いわゆるひとつの( ´∀`)<ぬるぽ、■━⊂( ・∀・) 彡 ガッ☆`Д´)ノってやつですね

scala.Nothingクラス

全てのクラスのサブクラスになるのがNothingクラスですね、なのでNullクラスのように互換性がないクラスがないのでイロイロなところで使えますよー、ちなみに一番使われるのはエラー処理のときの戻りみたいです

例えばこんなふうに使われマス(Scala標準ライブラリーのPrefdefオブジェクトのerrorメソッドらしデス)

// エラー発行後にNothing型が戻りますよー
def error(message:String):Nothing = throw new RuntimeException(message)

あと値型とも互換性があるのでこんなことも出来マスね

// 割り算をするメソッドです
def divide(x:Int, y:Int):Int =
 // 分母が0の場合は上記errorメソッドでエラーを発行します
  if(y != 0) x / y else error("エラーです")

上記divideメソッドはInt型を返しますが、互換性があるのでNothing型を返してもモウマンタイみたいです

以上ー

座学的11章は終わりです。ようやく3分の1ですねー、次は12章のトレイトですよー