Last active
February 2, 2024 01:24
-
-
Save matsu-chara/dde005b46c5a7218ace9 to your computer and use it in GitHub Desktop.
case classをtupleに変換したり、tupleをcase classに変換できるIso trait
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name := "Iso" | |
version := "1.0" | |
scalaVersion := "2.11.6" | |
libraryDependencies += "org.scala-lang" % "scala-reflect" % scalaVersion.value |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Deriving | |
// (だいたい)任意のcase classをマクロでtupleに変換 | |
object DerivingTest extends App { | |
// テスト用ケースクラス | |
case class Bar() | |
case class Foo(i: Int, s: String, d: Double) | |
case class Baz(i: Int, s: String, d: Double, f: Foo) | |
// isoインスタンス | |
val barIso = implicitly[Iso[Bar, Unit]] | |
val fooIso = implicitly[Iso[Foo, (Int, String, Double)]] | |
val bazIso = implicitly[Iso[Baz, (Int, String, Double, Foo)]] | |
// フィールド数0のクラス | |
val bar : Bar = Bar() | |
val tupleFromBar: Unit = barIso.to(bar) | |
val barFromTuple: Bar = barIso.from(tupleFromBar) | |
println(s"$tupleFromBar, $barFromTuple") | |
// 普通のクラス | |
val foo = Foo(23, "foo", 4.3) | |
val tupleFromFoo = fooIso.to(foo) | |
val fooFromTuple = fooIso.from(tupleFromFoo) | |
println(s"$foo, $tupleFromFoo, $fooFromTuple") | |
// ネストした型 | |
val baz = Baz(1, "b", 2.3, fooFromTuple) | |
val tupleFromBaz = bazIso.to(baz) | |
val bazFromTuple = bazIso.from(tupleFromBaz) | |
println(s"$baz, $tupleFromBaz, $bazFromTuple") | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package Deriving | |
import scala.language.experimental.macros | |
import scala.reflect.macros.whitebox | |
trait Iso[T, U] { | |
def to(t: T): U | |
def from(u: U):T | |
} | |
object Iso { | |
implicit def materializeIso[T, U]: Iso[T, U] = macro impl[T, U] | |
def impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: whitebox.Context): c.Expr[Iso[T, U]] = { | |
import c.universe._ | |
// case classのクラス名 | |
val caseClassSym: c.universe.Symbol = c.weakTypeOf[T].typeSymbol | |
if (!caseClassSym.isClass || !caseClassSym.asClass.isCaseClass) c.abort(c.enclosingPosition, s"$caseClassSym is not a case class") | |
// case classのフィールドに関するobject | |
object Fields { | |
// 各フィールドのシンボル | |
val syms: List[TermSymbol] = caseClassSym.typeSignature.decls.toList.collect { case x: TermSymbol if x.isVal && x.isCaseAccessor => x } | |
// List(Int, String, Int) のようなフィールドの型名を並べたリスト | |
val types: List[Tree] = syms map (f => tq"${f.typeSignature}") | |
// Lost(t.foo, t.bar, t.baz) のように 「t.フィールド名」 と並べたリスト (tはtoで使えるように、toの引数名に合わせている) | |
// 本当はt.nameとしたいが、t.`foo` となってしまうので、 t.fooにするためにstring化→trimして回避している | |
val names: List[Tree] = syms map (f => q"t.${TermName(f.name.toString.trim)}") | |
} | |
import Fields._ | |
// case classと同型なタプルの型 | |
// Tuple3[Int, String, Int]など | |
def tupleType: Tree = tq"(..$types)" | |
// case classと同型なタプルのインスタンス | |
// (1, "aaa", 2)など | |
def toImpl: Tree = q"(..$names)" | |
// タプルと同型なcase classのインスタンス | |
// case classのフィールドが0個のときはapply.tupledが無いので直接インスタンス化する | |
// Foo(1, "aaa" 2)など | |
def fromImpl: Tree = q"${if(syms.isEmpty) q"${caseClassSym.companion}.apply" else q"${caseClassSym.companion}.tupled(u)"}" | |
// Iso[T, U]の無名サブクラスを定義してnew | |
val evidence: Tree = q""" | |
final class $$anon extends Iso[$caseClassSym, $tupleType] { | |
def to(t: $caseClassSym): $tupleType = $toImpl | |
def from(u: $tupleType): $caseClassSym = $fromImpl | |
} | |
new $$anon | |
""" | |
c.Expr[Iso[T, U]](evidence) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment