Type-Classパターンのおぼえがき
Qiitaからの転記です。
Type-class
パターンの覚え書きです。
使わないと忘れてしまう自分用メモです。
Type-classを使う前の実装
これだと以下のパターンマッチを使ったコードがあったとして、
case class User(name: String, age: Int, email: String) object HTMLSerializerPatternMatch { def serialize(value: Any): String = value match { case User(name, age, email) => s"<div>${name} (${age} yo) <a href=${email}/> </div>" case i: Int => s"<div style: color=blue>$value</div>" }
良くない点が以下。
- 型安全でない。(Anyを使っている)
- マッチングしたい型が増えたときに、毎回コードの修正が発生する。
上記のコードをType-classを使って改善する。
Type-classを使った実装
// 型クラス trait HTMLSerializer[T] { def serialize(value: T): String } // 型クラスのコンパニオンオブジェクト object HTMLSerializer { def serialize[T](value: T)(implicit serializer: HTMLSerializer[T]): String = serializer.serialize(value) def apply[T](implicit serializer: HTMLSerializer[T]) = serializer } // 型クラスのインスタンスがInt implicit object IntSerializer extends HTMLSerializer[Int] { override def serialize(value: Int): String = s"<div style: color=blue>$value</div>" } // 型クラスのインスタンスがUser implicit object UserSerializer extends HTMLSerializer[User] { override def serialize(user: User): String = s"<div>${user.name} (${user.age} yo) <a href=${user.email}/> </div>" }
implicit serializer: HTMLSerializer[T]
のserializer
にIntSerializer
もしくはUserSerializer
が暗黙的に渡される。Stragegyパターンに似たことをimplicit
で表現できる。
val value1 = User("48hands", 48, "hogehoge@example.com") val value2 = 48 println(HTMLSerializer.serialize(value1)) println(HTMLSerializer.serialize(value2))
Type-classとimplicit conversion class組み合わせ
以下のようなimplicitなクラスを定義する。
T
にはUser
とInt
が入ることになる。
implicit class HTMLEnricher[T](value: T) { def toHTML(implicit serializer: HTMLSerializer[T]): String = serializer.serialize(value) }
以下のように利用できる。
val user = User("48hands", 22, "48hands@example.com") val number = 48 println(user.toHTML) println(number.toHTML)
実行結果。
<div>48hands (22 yo) <a href=48hands@example.com/> </div> <div style: color=blue>48</div>
コード全部のせておきます。
case class User(name: String, age: Int, email: String) package object serializer { // 型クラス trait HTMLSerializer[T] { def serialize(value: T): String } // 型クラスのコンパニオンオブジェクト object HTMLSerializer { def serialize[T](value: T)(implicit serializer: HTMLSerializer[T]): String = serializer.serialize(value) def apply[T](implicit serializer: HTMLSerializer[T]) = serializer } // 型クラスのインスタンス implicit object UserSerializer extends HTMLSerializer[User] { override def serialize(user: User): String = s"<div>${user.name} (${user.age} yo) <a href=${user.email}/> </div>" } // 型クラスのインスタンス implicit object IntSerializer extends HTMLSerializer[Int] { override def serialize(value: Int): String = s"<div style: color=blue>$value</div>" } } package object richer { implicit class HTMLEnricher[T](value: T) { import serializer._ def toHTML(implicit serializer: HTMLSerializer[T]): String = serializer.serialize(value) } } // エンドポイント object TypeClassSandbox extends App { import richer._ val user = User("48hands", 22, "48hands@example.com") val number = 48 println(user.toHTML) println(number.toHTML) }
(object HTMLSerializer
はなくても動きます。)
まとめると、基本構成はざっくり以下になっている、
// Type class trait MyTypeClassTemplate[T] { def doSomething(value: T): String }
// Type class instances implicit object MyTypeClassInstance extends MyTypeClassTemplate[Int] { override def doSomething(value: Int): String = ??? }
// Enriching types with type classes implicit class ConversionClass[T](value: T) { def doSomething(implicit instance: MyTypeClassTemplate[T]): String = instance.doSomething(value) }