【问题标题】:How to automatically inherit mixin generic type in self-types?如何在 self-types 中自动继承 mixin 泛型类型?
【发布时间】:2020-12-20 10:20:57
【问题描述】:

如何从父 mixin 类型继承泛型类型?例如,我有一个特征 Foo 和一个泛型类型 A

trait Foo[A] {
  def value: A
}

我有一个使用Foo[String] 的类User,例如:

class User extends Foo[String] {
  override def value: String = ???
}

一切正常。现在,我想添加一个自我类型为Foo[A] 的特征Bar[A]

trait Bar[A] { self: Foo[A] =>
  def anotherValue: A
}

如果我想在User 中使用Bar,我需要这样做:

class User extends Foo[String] with Bar[String] {
  override def value: String = ???
  override def anotherValue: String = ???
}

无论如何我可以将User 简化为这个? (Bar 自动从对应的Foo 推断类型。)

class User extends Foo[String] with Bar 

【问题讨论】:

  • 为什么需要这个? extends Foo[String] with Bar 并不比 extends Foo[String] with Bar[String] 短多少。
  • 如果不是String会变得很长。另外我觉得Bar的类型A应该很明显,编译器应该可以推断出来。
  • 为什么不定义更短的类型别名? type A = SomethingLongerThanString.
  • 应该很明显,编译器应该能够推断它 这不是推断,而是语法。如果你有Bar[A],那么extends Bar 是非法的。
  • 是的,这就是为什么我在想是否有办法使用其他东西,例如证据。

标签: scala generics inheritance self-type


【解决方案1】:

你可以定义中间特征

trait UserLike[A] extends Foo[A] with Bar[A]

class User extends UserLike[String] {
  override def value: String = ???
  override def anotherValue: String = ???
}

你可以定义一个macro annotation,但这有点过头了

@extendsBar
class User extends Foo[String] {
  override def value: String = ???
  override def anotherValue: String = ???
}

//scalac: {
//  class User extends Foo[String] with Bar[String] {
//    def <init>() = {
//      super.<init>();
//      ()
//    };
//    override def value: String = $qmark$qmark$qmark;
//    override def anotherValue: String = $qmark$qmark$qmark
//  };
//  ()
//}

import scala.annotation.StaticAnnotation
import scala.language.experimental.macros
import scala.reflect.macros.blackbox

class extendsBar extends StaticAnnotation {
  def macroTransform(annottees: Any*): Any = macro ExtendsBarMacro.impl
}

object ExtendsBarMacro {
  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 fooArg = parents.collectFirst {
          case tq"Foo[$t]" => t
        }.getOrElse(c.abort(c.enclosingPosition, "class must extend Foo"))
        val parents1 = parents :+ tq"Bar[$fooArg]"
        q"""
           $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents1 { $self => ..$stats }
           ..$tail
        """
      case _ =>
        c.abort(c.enclosingPosition, "annottee must be a class")
    }
  }
}

如果A 是类型成员而不是类型参数,这将更加灵活

trait Foo {
  type A
  def value: A
}

trait Bar { self: Foo =>
  def anotherValue: A
}

class User extends Foo with Bar {
  override type A = String
  override def value: String = ???
  override def anotherValue: String = ???
}

【讨论】:

  • 是的,我也想过这个,但是如果你有多个 Bar mixins 以及它们的所有组合,那就很痛苦了。 (想想Bar1Bar2Bar3;你将有 7 种不同的组合,因此有 7 个中间特征。)有没有办法在没有中间特征的情况下做到这一点? (例如,也许使用证据?)
  • @krismath 你可以定义一个宏注解。见更新。但这有点矫枉过正。
  • @krismath 也许使用证据 你不会让一个类使用证据来扩展某些东西。你有足够的证据吗?它们比继承弱。
  • @krismath 如果A 是类型成员,这将更加灵活。见更新。顺便说一句,你为什么不能让Bar 扩展Foo 而不是使用自我类型。
  • 我记得我需要所有Bars 来共享相同的Foo.value(可以说设计可以改进,但我们离题了)。接受了答案,因为这似乎是我能得到的最好的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-27
  • 2015-09-15
  • 1970-01-01
  • 2019-03-07
  • 1970-01-01
  • 2021-10-11
  • 2018-08-01
相关资源
最近更新 更多