テストステ論

高テス協会会長が, テストステロンに関する情報をお届けします.

(scala report) パッケージオブジェクトと暗黙クラス変換

2.8から導入されたパッケージオブジェクト. これを見た時, 私には,
「package object a.bを作るのと, a.b.PackageObjectというオブジェクトを作るのとで差はどこにあるのか?」
という疑問が浮かんだが, これは
「パッケージオブジェクトに定義されたものはそのパッケージの中から可視となる」 という回答で受理.

この違いは例えば, パッケージで共有の暗黙変換ユーティリティなどを定義したい場合に役立つ. パッケージオブジェクトがimplicitを含んでいる場合はある. 例えば, pickling.binary. 一方で, JavaConversionsのように, 明示的にオブジェクトとして分離させている場合もある. これらを分かつのは, 単なる設計判断. ライブラリであれば, internalパッケージを定義して, implicitがパッケージの外に必要以上に拡散するのを防ぐというテクニックはあり得る.このimplicitの一部を外に公開させたいのであれば, traitとして切り出して共有するという設計変更をしてもいいだろう. ここではimpilcitを例としたが, ふつうのpackageに置くことの出来ない(つまりトップレベルに置くことの出来ない)コードをパッケージオブジェクトに置くと, コードを適切に分割しつつ, 煩雑なimportを避けることが可能になると思う.

(ただし, この程度ならばCのヘッダでも同じことではないか?)

パッケージオブジェクトの概要は: http://www.scala-lang.org/docu/files/packageobjects/packageobjects.html

簡単な例:

まず, コンパイル出来ないケース

// Error:(5, 18) `implicit' modifier cannot be used for top-level objects
//   implicit class T(n:Int)
//                  ^
package my.pobj.pre {
  implicit class T(n:Int)
}

次に, コンパイル出来る例:

package my
trait MyTrait {
  implicit class S(b: Boolean) {
    def say = println("hello")
  }
}
package object pobj extends MyTrait {
  implicit class T(n: Int) {
    def say = println("farewell")
  }
}
object PackageTest extends App {
  import my.pobj._
  true.say // hello
  1.say // farewell
}

Scalaを使った設計の基本パターンは,

  1. traitに実装を分離する.
  2. objectがtraitをmix-inする.
  3. 場合によっては, そのobjectはパッケージオブジェクトである.

という感じになると思う. 補足のために, http://stackoverflow.com/questions/3400734/package-objects の35を読むと良い. 実例として, picklingから以下のコードを紹介する.

package scala.pickling

import scala.pickling.pickler.AllPicklers

/** Import `scala.pickling.Defaults._` to introduce all picklers and ops.
 */
object Defaults extends Ops with AllPicklers {}