是否可以让子特征从另一个特征继承类参数?
Posted
技术标签:
【中文标题】是否可以让子特征从另一个特征继承类参数?【英文标题】:Is it possible to have a sub-trait inheirit a class parameter from another trait? 【发布时间】:2021-10-28 21:30:30 【问题描述】:我正在尝试稍微干掉我的代码。我正在使用 Circe 进行一些解码。我有几个班级,所有班级的形式都是:
import io.circe.derivation.deriveDecoder
import io.circe.derivation.renaming.snakeCase
import io.circe.parser.decode
import io.circe.Decoder, Error
// Getter[A] just defines some functions for getting the data from an endpoint.
class JSONGetter extends Getter[MyClass]
implicit val decoder: Decoder[MyClass] = deriveDecoder[MyClass](io.circle.derivation.renaming.snakeCase)
// .. other parsing code here
我不想再重复自己的含蓄,所以我开始创造一个新的特质:
trait JsonDecoding[A]
implicit val decoder: Decoder[A] = deriveDecoder[A](io.circle.derivation.renaming.snakeCase)
这样我就可以将我的课程缩短到:
class JSONGetter extends Getter[MyClass] with JsonDecoding[MyClass]
// .. other parsing code here
这样会很方便。但是,我在尝试编译时得到A is not a class
。我认为我不能按照我想要的方式做到这一点。
有没有一种聪明的方法可以做到这一点,所以在定义只在被解码的类中发生变化的隐式解码器时我不能重复自己?
【问题讨论】:
【参考方案1】:您可以使用automatic derivation
import io.circe.generic.auto._
case class Person(name: String)
而不是semi-automatic derivation
io.circe.generic.semiauto
case class Person(name: String)
object Person
implicit val fooDecoder: Person[Foo] = semiauto.deriveDecoder
implicit val fooEncoder: Person[Foo] = semiauto.deriveEncoder
或宏注解@JsonCodec简化半自动推导
import io.circe.generic.JsonCodec
@JsonCodec case class Person(name: String)
假设您更喜欢半自动推导而不是自动推导。
扩展特征是一种错误的方式
class JSONGetter extends Getter[MyClass] with JsonDecoding[MyClass]
// ...
问题是deriveDecoder
是一个宏,在适当的位置扩展宏很重要。如果你扩展一个 trait 并在其中隐式放置,那么宏会在不正确的位置展开。
您可以定义自己的macro annotation,这将添加必要的隐式
@jsonDecoding
class JSONGetter extends Getter[MyClass]
import scala.annotation.StaticAnnotation, compileTimeOnly
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
@compileTimeOnly("enable macro paradise to expand macro annotations")
class jsonDecoding extends StaticAnnotation
def macroTransform(annottees: Any*): Any = macro jsonDecodingMacro.impl
object jsonDecodingMacro
def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree =
import c.universe._
annottees match
case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$earlydefns with ..$parents $self => ..$stats " :: tail =>
val tparamNames = tparams.map
case q"$mods type $tpname[..$tparams] = $tpt" => tpname
q"""
$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends ..$earlydefns with ..$parents $self =>
..$stats
implicit val decoder: _root_.io.circe.Decoder[$tpname[..$tparamNames]] =
_root_.io.circe.derivation.deriveDecoder[$tpname[..$tparamNames]](_root_.io.circe.derivation.renaming.snakeCase)
..$tail
"""
// or should the implicit be added to companion object?
case q"$mods object $tname extends ..$earlydefns with ..$parents $self => ..$body " =>
// ...
case q"$mods trait $tpname[..$tparams] extends ..$earlydefns with ..$parents $self => ..$stats " =>
//...
Scala | How can this code be put into a macro annotation?
How to reduce boilerplate code with Scala Macros in Scala 2?
Pass implicit parameter through multiple objects
Scala macro-based annotation reuse
对于Cats 类型类,您也可以使用Kittens 以原子方式派生类型类
import cats.derived.auto.functor._
case class Cat[Food](food: Food, foods: List[Food])
或半自动
import cats.derived.semiauto
case class Cat[Food](food: Food, foods: List[Food])
object Cat
implicit val fc: Functor[Cat] = semiauto.functor
如果您更喜欢半自动派生,那么您可以使用 Katnip 宏注释,而不是为每个类编写必要的隐式 semiauto.functor
import io.scalaland.catnip.Semi
@Semi(Functor) case class Cat[Food](food: Food, foods: List[Food])
【讨论】:
以上是关于是否可以让子特征从另一个特征继承类参数?的主要内容,如果未能解决你的问题,请参考以下文章