【问题标题】:Returning the same type from abstract trait method从抽象特征方法返回相同的类型
【发布时间】:2014-11-24 04:34:06
【问题描述】:

假设我们有一个特征,它有一些值和一些操作。

trait Foo {
  type Self <: Foo
  val x: Int
  def withX(x: Int): Self
}

这是使用抽象类型实现的。我们在 Self 上绑定了一个类型,可以像这样实现它:

case class Foo1(x: Int) extends Foo {
  type Self = Foo1
  def withX(x: Int) = copy(x = x)
}

没关系。我们可以使用该方法,我们看到类型是静态保留的。

scala> Foo1(10).withX(5)
res0: Foo1 = Foo1(5)

当我们想要一个具有特征类型而不是具体类型的操作时,问题就开始了:

object Foo {
//Error:(13, 43) type mismatch;
//found   : f.Self
//required: A
//  def setFive[A <: Foo](f: A): A = f.withX(5)
}

好吧,我们不能完全做到这一点,因为编译器不知道 Foo#Self 将被分配到什么类型。但我们知道它是同一类型。

当然,使用丑陋的方法也可以:

object Foo {
  // Ugly type signature
  def setFiveValid[A <: Foo](f: A): A#Self = f.withX(5)

  // Another ugly type signature
  def setFiveValid2[A <: Foo](f: A): f.Self = f.withX(5)
}

他们都没有很清楚地表达意图。

我们可以使用类型类来解决它。

case class Foo2(x: Int)

trait FooOps[A] extends Any {
  def a: A
  def withX(x: Int): A
}

object Foo2 {
  implicit class Foo2Ops(val a: Foo2) extends AnyVal with FooOps[Foo2] {
    def withX(x: Int) = a.copy(x = x)
  }
}

object Foo {
  // View bounds approach.
  def setFiveValid3[A <% FooOps[A]](f: A): A = f.withX(5)
}

不过这还是很吵。

有没有更好的方法来实现setFive

编辑 1

self 类型的主要问题是这样的:

Error:(24, 11) type mismatch;
 found   : app.models.world.WObject.WorldObjUpdate[self.Self] => app.models.world.WObject.WorldObjUpdate[self.Self]
    (which expands to)  app.models.game.events.Evented[(app.models.world.World, self.Self)] => app.models.game.events.Evented[(app.models.world.World, self.Self)]
 required: app.models.world.WObject.WorldObjUpdate[self.Self] => app.models.game.events.Evented[(app.models.world.World, Self)]
    (which expands to)  app.models.game.events.Evented[(app.models.world.World, self.Self)] => app.models.game.events.Evented[(app.models.world.World, Self)]
          identity
          ^

然后再次使用看起来很奇怪的签名和样板:

  def attackReachable(
    data: WObject.WorldObjUpdate[Self]
  ): WObject.WorldObjUpdate[data.value._2.Self]

【问题讨论】:

    标签: scala typeclass traits abstract-type


    【解决方案1】:

    你可以走“F-bounded quantification”这条路:

    trait Foo[F <: Foo[F]] {
      def withX(x: Int): F
    }
    
    object Foo {
      def setFive[F <: Foo[F]](f: F): F = f.withX(5)
    }
    

    我多次成功地使用它,但代价是不得不在任何地方写F &lt;: Foo[F]]

    【讨论】:

    • 是的,这不是更干净,我忘了提到我试图避免这种情况。基本上我正在寻找样板最少的解决方案。
    • 祝您搜索顺利,但我必须警告您;我已经尝试了所有其他场景,如果没有这种方法提供的“闭环”,你就不会走得太远。没有循环的类型投影将无法对返回类型做很多事情。
    【解决方案2】:

    最好的签名是你建议的依赖路径的类型:

    // Another ugly type signature
    def setFiveValid2[A <: Foo](f: A): f.Self = f.withX(5)
    

    你甚至不需要类型参数。将f 输入为Foo 即可(除非您在实际上下文中需要A 来获取其他内容):

    def setFiveValid3(f: Foo): f.Self = f.withX(5)
    

    这并不难看。相反,它是路径依赖类型的完美用途之一。当您说它没有清楚地表达意图时,我也不同意:您清楚地说明结果将具有您给出的论点的 Self 类型。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-11-19
      • 2013-08-14
      • 1970-01-01
      • 1970-01-01
      • 2023-01-12
      • 1970-01-01
      • 2013-04-16
      • 2014-04-03
      相关资源
      最近更新 更多