首页 > 解决方案 > 是否可以让子特征继承另一个特征的类参数?

问题描述

我正在尝试稍微干燥我的代码。我正在使用 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在尝试编译时得到了。我认为我不能以我想要的方式做到这一点。

有没有一种聪明的方法可以做到这一点,所以在定义只在被解码的类中发生变化的隐式解码器时我不能重复自己?

标签: scalatraitsimplicitgeneric-derivation

解决方案


您可以使用自动推导

import io.circe.generic.auto._

case class Person(name: String)

而不是半自动推导

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是一个宏,在适当的位置扩展宏很重要。如果您扩展一个特征并将隐式放在那里,那么宏会在不正确的位置展开。

您可以定义自己的宏注释,将添加必要的隐式

@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 2 中使用 Scala 宏减少样板代码?

通过多个对象传递隐式参数

Scala 基于宏的注解重用

同样,对于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])

推荐阅读