私の頭の上、上記とオフを解決した後、必要なものを自動生成するのは簡単なはずです。代わりに暗黙のマクロを使用することもできます。これは型付けされ、マクロ・パラダイスは必要ありませんが、議論のために、これは定型文をスキップする簡単な方法です。私たちは、MyType
のコンパニオンオブジェクトの内部Functional[MyType]
型クラスの暗黙のインスタンスを生成している
、またはあなたのケースで我々はコンパニオンオブジェクトを生成しているobject Price { implicit object bla extends Fractional[Price] { .. } }
我々は理由Scalaは自動的に内部で暗黙を探すことができますこの方法でこれを行いますコンパニオンオブジェクトであるため、明示的にインポートする必要はありません。
@macrocompat.bundle
class FractionalMacro(val c: scala.reflect.macros.blackbox.Context) {
import c.universe._
/**
* Retrieves the accessor fields on a case class and returns an iterable of tuples of the form Name -> Type.
* For every single field in a case class, a reference to the string name and string type of the field are returned.
*
* Example:
*
* {{{
* case class Test(id: UUID, name: String, age: Int)
*
* accessors(Test) = Iterable("id" -> "UUID", "name" -> "String", age: "Int")
* }}}
*
* @param params The list of params retrieved from the case class.
* @return An iterable of tuples where each tuple encodes the string name and string type of a field.
*/
def accessors(
params: Seq[c.universe.ValDef]
): Iterable[(c.universe.TermName, c.universe.TypeName)] = {
params.map {
case ValDef(_, name: TermName, tpt: Tree, _) => name -> TypeName(tpt.toString)
}
}
def makeFunctional(
tpe: c.TypeName,
name: c.TermName,
params: Seq[ValDef]
): Tree = {
val fresh = c.freshName(name)
val applies = accessors(params).headOption match {
case Some(field) => field._1
case None => c.abort(c.enclosingPosition, "Expected one arg")
}
q"""implicit object $fresh extends scala.math.Fractional[$tpe] {
// Ordering:
def compare(x: $tpe, y: $tpe): Int = x.$field compare y.$field
// Numeric:
def plus(x: $tpe,y: $tpe): $tpe = $name(x.$field + y.$field)
def minus(x: $tpe,y: $tpe): $tpe = $name(x.$field - y.$field)
def times(x: $tpe, y: $tpe): $tpe = $name(x.$field * y.$field)
def negate(x: $tpe): $tpe = $name(-x.$field)
def fromInt (x: Int): $tpe = $name(x.$field.toDouble)
def toInt (x: $tpe): Int = x.$field.toInt
def toLong (x: $tpe): Long = x.$field.toLong
def toFloat (x: $tpe): Float = x.$field.toFloat
def toDouble(x: $tpe): Double = x.$field
// Fractional:
def div(x: $tpe, y: $tpe): $tpe = $name(x.value/y.value)
}
}"""
}
def macroImpl(annottees: c.Expr[Any]*): Tree =
annottees.map(_.tree) match {
case (classDef @ q"$mods class $tpname[..$tparams] $ctorMods(...$params) extends { ..$earlydefns } with ..$parents { $self => ..$stats }")
:: Nil if mods.hasFlag(Flag.CASE) =>
val name = tpname.toTermName
val res = q"""
$classDef
object $name {
..${makeFunctional(tpname.toTypeName, name, params.head)}
}
"""
println(showCode(res))
res
case _ => c.abort(c.enclosingPosition, "Invalid annotation target, Sample must be a case classes")
} }
加えて、あなたはそれは、知られている数学的なタイプであることを確認しますするフィールドをチェックし入力するか、または使用することができ、必要であれば、あなたが暗黙のスコープ内に委任することができるように該当する場合Numeric
暗黙の。
は今、かなりあなたが必要なのは次のとおりです。
@compileTimeOnly("Enable macro paradise to expand macro annotations")
class fractional extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro FractionalMacro.macroImpl
}
@fractional case class Price(value: Double)
暗黙のマクロがで以来、あなたはそのコンパニオンオブジェクトへの暗黙的なマクロmaterialiserへの参照を追加するためにFractional
の内容を編集することができれば、可能性がありますが、この場合、デフォルトのライブラリを編集することはできません。これは、必要な暗黙のインポートを明示的にインポートする必要がない、よりクールな方法です。
ボーナスは、おそらくより多くのフィールドとより複雑なものに対応するために拡張される可能性があります。
Spireライブラリ(https://github.com/non/spire)をご覧ください。しかし、余計かもしれませんが – Eduardo
@Eduardoとてもクールです!しかし、私は標準ライブラリを使用したソリューションを好むでしょう。 – davidrpugh
潜在的な暗黙的な変換はどうですか? – Samar