Возможно ли, чтобы суб-признак унаследовал параметр класса от другого признака?
Я пытаюсь немного ОСУШИТЬ свой код. Я использую Цирцею для декодирования. У меня несколько классов, и все они имеют вид:
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
}
Это было бы очень удобно. Однако я получаю
Есть ли разумный способ сделать это, чтобы я не мог повторяться при определении неявного декодера, который изменяется только в декодируемом классе?
1 ответ
Вы можете использовать автоматический вывод
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 | Как этот код можно поместить в аннотацию макроса?
Как уменьшить шаблонный код с помощью макросов Scala в Scala 2?
Передавать неявный параметр через несколько объектов
Повторное использование аннотаций на основе макросов 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])