【问题标题】: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。我认为我不能按照我想要的方式做到这一点。

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

【问题讨论】:

    标签: scala traits implicit generic-derivation


    【解决方案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])
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-11
      • 1970-01-01
      • 2013-12-21
      • 2018-06-02
      • 1970-01-01
      相关资源
      最近更新 更多