Scala Language
型クラス
サーチ…
備考
直列化の問題、特に分散環境( Apache Sparkなど )での問題を避けるため、型クラス・インスタンス用のSerializable
な特性を実装することがベスト・プラクティスです。
シンプルタイプクラス
型クラスは、1つ以上の型パラメータを持つ単なるtrait
です。
trait Show[A] {
def show(a: A): String
}
型クラスを拡張する代わりに、サポートされている型ごとに型クラスの暗黙のインスタンスが用意されています。これらの実装を型クラスのコンパニオンオブジェクトに配置することで、特別なインポートをせずに暗黙の解決を行うことができます。
object Show {
implicit val intShow: Show[Int] = new Show {
def show(x: Int): String = x.toString
}
implicit val dateShow: Show[java.util.Date] = new Show {
def show(x: java.util.Date): String = x.getTime.toString
}
// ..etc
}
関数に渡される汎用パラメータに型クラスのインスタンスがあることを保証したい場合は、暗黙のパラメータを使用します。
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
コンテキストバインドを使用することもできます 。
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
他の方法と同様に、上記のlog
メソッドを呼び出しlog
。暗黙の場合はコンパイルに失敗しますShow[A]
の実装が見つからないA
あなたがに渡すlog
log(10) // prints: "10"
log(new java.util.Date(1469491668401L) // prints: "1469491668401"
log(List(1,2,3)) // fails to compile with
// could not find implicit value for evidence parameter of type Show[List[Int]]
この例では、 Show
型のクラスを実装しています。これは、任意の型の任意のインスタンスをString
に変換するために使用される共通の型クラスです。すべてのオブジェクトが持っているにもかかわらずtoString
メソッドを、それがいるか否かは必ずしも明確ではないtoString
便利な方法で定義されています。 Show
型クラスを使用すると、 log
渡されたものがString
への明確な変換を持つことを保証することができlog
。
型クラスの拡張
この例では、以下の型クラスの拡張について説明します。
trait Show[A] {
def show: String
}
あなたがコントロールするクラスを作成するには(そしてScalaで書かれています)、型クラスを拡張し、コンパニオンオブジェクトに暗黙的に追加します。 この例からPerson
クラスを取得してShow
を拡張する方法を示しましょう:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
このクラスは、 Person
のコンパニオンオブジェクトに暗黙的に追加することで、 Show
を拡張することができます:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
コンパニオンオブジェクトはクラスと同じファイルになければならないので、 class Person
とobject Person
両方が同じファイルに必要です。
Scalaで制御しないクラスを作成するには、 単純型クラスの例に示すように、型クラスを拡張し、型クラスのコンパニオンオブジェクトに暗黙的に追加します。
クラスも型クラスも制御しない場合は、上記のように暗黙的に作成してimport
します。 シンプル・タイプ・クラスの例でのlog
メソッドの使用:
object MyShow {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
def logPeople(persons: Person*): Unit = {
import MyShow.personShow
persons foreach { p => log(p) }
}
型に型関数を追加する
Scalaの型クラスの実装はかなり冗長です。冗長性を減らす1つの方法は、いわゆる "操作クラス"を導入することです。これらのクラスは、インポート時に変数/値を自動的にラップして機能を拡張します。
これを説明するために、まず単純な型クラスを作成しましょう:
// The mathematical definition of "Addable" is "Semigroup"
trait Addable[A] {
def add(x: A, y: A): A
}
次に、特性を実装します(型クラスをインスタンス化します)。
object Instances {
// Instance for Int
// Also called evidence object, meaning that this object saw that Int learned how to be added
implicit object addableInt extends Addable[Int] {
def add(x: Int, y: Int): Int = x + y
}
// Instance for String
implicit object addableString extends Addable[String] {
def add(x: String, y: String): String = x + y
}
}
現時点では、Addableインスタンスを使用することは非常に面倒です。
import Instances._
val three = addableInt.add(1,2)
むしろ1.add(2)
書くだけです。したがって、私たちはAddable
を実装する型を常にラップする "Operation Class"( "Ops Class"とも呼ばれる)をAddable
ます。
object Ops {
implicit class AddableOps[A](self: A)(implicit A: Addable[A]) {
def add(other: A): A = A.add(self, other)
}
}
今度は、新しい関数add
を、それがInt
とString
一部であるかのように使用できます。
object Main {
import Instances._ // import evidence objects into this scope
import Ops._ // import the wrappers
def main(args: Array[String]): Unit = {
println(1.add(5))
println("mag".add("net"))
// println(1.add(3.141)) // Fails because we didn't create an instance for Double
}
}
"Ops"クラスは、 simulacrumライブラリのマクロによって自動的に作成されます:
import simulacrum._
@typeclass trait Addable[A] {
@op("|+|") def add(x: A, y: A): A
}