MagnoliaはScala界隈ではおなじみのJon Prettyさん作のライブラリで、簡単に言うとケースクラスに対する型クラスのインスタンスを生成するためのマクロを簡単に実装できるライブラリです。Play-JSONのReads
、Writes
、Format
などのマクロなどを簡単に作れるものというとわかりやすいでしょうか。Scala eXchange 2017のとあるセッションでちょっとだけ紹介されていて面白そうだったので軽く触ってみました。
github.com
わかりやすい例としてケースクラスの内容を文字列表現に変換するShow
という型クラスを作ってみます(ScalaのケースクラスはtoString
するだけで十分わかりやすい出力を得ることができるので敢えて作る必要はないのですがMagnoliaのドキュメントにも書かれているので練習ということで…)。
まずは以下のトレイトを定義します。
trait Show[T]{
def show(value: T): String
}
Derivationというオブジェクトを以下のような感じで実装します。
import language.experimental.macros, magnolia._
object ShowDerivation {
type Typeclass[T] = Show[T]
def combine[T](ctx: CaseClass[Show, T]): Show[T] = new Show[T] {
def show(value: T): String = {
ctx.parameters.map { p =>
s"${p.label}=${p.typeclass.show(p.dereference(value))}"
}.mkString("{", ",", "}")
}
}
def dispatch[T](ctx: SealedTrait[Show, T]): Show[T] = ???
implicit def gen[T]: Show[T] = macro Magnolia.gen[T]
}
見た目でなんとなくわかると思いますが、combine
メソッドにケースクラスに対するshow
メソッドの実装が記述されています。decode
メソッドはsealedトレイトを扱わない場合は実装しなくても大丈夫です。今回はMagnoliaでどんなことができるかを見るだけなので???
にしておきます。
定義したShowDerivation
オブジェクトを実際に使ってみましょう。以下のようなケースクラスと関数を用意しておきます。
case class Address(zipCode: String, country: String, prefecture: String)
case class Person(name: String, address: Address)
def printCaseClass[T](value: T)(implicit s: Show[T]): Unit = {
println(s.show(value))
}
この関数を呼び出してみます。
implicit val showString = new Show[String]{
override def show(value: String) = value
}
implicit val showAddress = ShowDerivation.gen[Address]
implicit val showPerson = ShowDerivation.gen[Person]
val person = Person("takezoe", Address("150-0013", "Japan", "Tokyo"))
printCaseClass(person)
出力は以下のようになります。
{name=takezoe,address={zipCode=150-0013,country=Japan,prefecture=Tokyo}}
このようにMagnoliaを使うと自前でマクロを書くのに比べると簡単に型クラスのインスタンスを生成するマクロを定義することができます。GitHubのリポジトリにはサンプルもいくつか含まれているので参考になると思います。
性質的にアプリケーションコードではなくライブラリやフレームワーク側で使うものだと思いますので利用頻度はさほど高くないかもしれませんが目の付け所が面白いライブラリですね。