【问题标题】:What is the difference between MyClass and MyClass.this.type and how to turn one to another?MyClass 和 MyClass.this.type 之间有什么区别以及如何将其转换为另一个?
【发布时间】:2019-06-21 12:27:30
【问题描述】:

我有类似这样的情况:

trait Abst{
  type T
  def met1(p: T) = p.toString
  def met2(p: T, f: Double=>this.type){
    val v = f(1.0)
    v.met1(p)
  }
}
class MyClass(x: Double) extends Abst{
  case class Param(a:Int)
  type T = Param
  val s = met2(Param(1), (d: Double) => new MyClass(d))
}

在我运行它之前它不会显示错误,然后它会说:

类型不匹配;找到:MyClass,必填: MyClass.this.type

我也尝试了泛型类型的解决方案,但后来我遇到了 this.T 与 v.T 不同的冲突

所以如果可能的话,我只需要克服上面的错误信息?


更新

因此,this.type 是该单个实例的 singleton type。我在评论中建议使用

val s = met2(Param(1), (d: Double) => (new MyClass(d)).asInstanceOf[this.type])

所以如果有人对此发表评论,我知道它有多丑陋,只是对它有多不安全感兴趣?

你们也都提议将 Param 的定义移到课堂之外,我绝对同意。所以它的定义会在伴生对象MyClass中

【问题讨论】:

  • 我通过将 .asInstanceOf[this.type] 添加到构造函数来成功运行它......现在好了,直到我找到更优雅的东西......
  • 您不希望fDouble => Abst 类型是否有特殊原因?我问是因为尽管有一些方法可以在 trait 中引用底层实现的类型,但它们不使用 this.type,总体上非常复杂,而且我实际上并没有看到通过避免超类型在这里。
  • @Astrid 在很多情况下,您需要处理子类而不是超类。
  • @Astrid 嗯,你是对的。实际上,我想到了子类具有的所有功能,但无论如何我都无法从超类方法访问它们。谢谢!我是多么蹩脚……
  • 不,(new MyClass(d)).asInstanceOf[this.type] 是个坏主意。 asInstanceOf 的使用应该保留在您确定知道正确类型是什么但无法通过静态类型检查证明的情况下。 asInstanceOf 用于承诺true 的事情,而不是做出明显错误的陈述。 (new MyClass(d)).asInstanceOf[this.type] 保证是错误的。请不要以这种方式使用asInstanceOf

标签: scala inheritance types


【解决方案1】:

this.typesingleton type,它被一个单一的值所占据,即this。因此,接受f: X => this.type 类型的函数作为参数肯定是没有意义的,因为f 的每次调用都可以被this 替换(加上f 执行的副作用)。

这是一种强制您的代码以最少更改进行编译的方法:

trait Abst { self =>
  type T
  def met1(p: T) = p.toString
  def met2(p: T, f: Double => Abst { type T = self.T }){
    val v = f(1.0)
    v.met1(p)
  }
}

case class Param(a:Int)
class MyClass(x: Double) extends Abst {
  type T = Param
  val s = met2(Param(1), (d: Double) => new MyClass(d))
}

但老实说:不要这样做。而且也不要做任何 F 有界的东西,它可能最终会变得一团糟,特别是如果你不熟悉这种模式。相反,重构你的代码,这样你就没有任何自引用螺旋。


更新

关于为什么告诉编译器 (new MyClass(d)) 的类型为 this.type 而其他 this: MyClass 是一个非常糟糕的主意:

abstract class A {
  type T
  val x: T
  val f: T => Unit
  def blowup(a: A): Unit = a.asInstanceOf[this.type].f(x)
}

object A {
  def apply[X](point: X, function: X => Unit): A = new A {
    type T = X
    val x = point
    val f = function
  }
}

val a = A("hello", (s: String) => println(s.size))
val b = A(42, (n: Int) => println(n + 58))

b.blowup(a)

尽管ab 都属于A 类型,但是这会导致ClassCastException

【讨论】:

  • 谢谢!是否要求 Param 在 MyClass 范围之外?也许我可以把它放在伴生对象里面?
  • @DjuraMarinkov 在上面的代码 sn-p 中,它是必需的,因为否则 MyClass 的每个实例都会有自己的 Param 类型,与任何其他 MyClass 参数不兼容。因此,除非您想将 MyClass 设为单例,否则将 Param 设为内部类没有任何意义。如果您决定将其设为单例,那么首先不清楚 Abst 特征有什么用处。目前还不清楚如何“改进”它,因为您的原始代码看起来像一个 XY 问题。也许您可以添加一两段关于您编写​​此代码的动机为什么
  • 实际上,一旦它是单例的,这就是为什么在里面定义了Param,但是现在我有了这个构造函数,它们就不再存在了。对不起,我想让代码简单明了,那里有很多很多方法,我以为我插入了所有相关的东西。我在我的问题中添加了几行,你可以看看
  • @DjuraMarinkov 等等,具体哪一部分不起作用?我很确定这两个 sn-ps 都应该编译,至少在 2.15 和更高版本下。
  • 2.11 这里...它已经在“self”关键字上中断...但其余部分也很可疑
【解决方案2】:

如果您不介意让 trait 将 T 作为通用参数,这是一个相当简单直接的等效解决方案:

trait Abst[T]{
  def met1(p: T) = p.toString
  def met2(p: T, f: Double=>Abst[T]){
    val v = f(1.0)
    v.met1(p)
  }
}

case class Param(a:Int)
class MyClass(x: Double) extends Abst[Param]{
  val s = met2(Param(1), (d: Double) => new MyClass(d))
}

我说它是等价的,因为让met2 使用超类型而不是子类型不会丢失任何信息。在特征中引用子类型的经典用例是例如有一个您想要返回 MyClass 而不是 Abst 的方法,即使它是在 Abst 中定义的,但这不是您所处的情况。唯一使用子类型引用的地方是 f 的定义,由于函数类型在其输出参数上是协变的,因此您可以毫无问题地将任何 f: Double => MyClass 传递给 f: Double => Abst[T]

如果您确实想引用子类型,请参阅 Markus 的回答...如果您确实想避免将 T 用作通用参数,那么事情又会变得更加复杂,因为现在您有潜在的冲突在AbstTmet2 定义中子类型的T 之间。

【讨论】:

  • 你说得对,这是最干净的解决方案。通常类型 T=Param 特定于每个 MyClass 类并且不会更改。这就是为什么它在体内而不是通用的原因
  • @DjuraMarinkov 我知道你来自哪里,但将T 保留在内部的问题是你在Abst 中有太多依赖它的逻辑。 met2 起作用的唯一方法是,如果您有某种说法“该函数应该创建一个 Abst 实现,其中它的 TpT 相同” - 换句话说,正是泛型为你做了什么。
【解决方案3】:

要克服此错误消息,您必须使用 F-bounded polymorphism

您的代码将如下所示:

trait Abst[F <: Abst[F, T], T]{ self: F =>

  def met1(p: T): String = p.toString

  def met2(p: T, f: Double => F): String = {
    val v = f(1.0)
    v.met1(p)
  }
}

case class Param(a:Int)

class MyClass(x: Double) extends Abst[MyClass, Param] {
  val s = met2(Param(1), (d: Double) => new MyClass(d))
}

解释:

在 trait 或类定义中使用 self: F =&gt; 会限制 this 的值。因此,如果 this 不是 F 类型,您的代码将无法编译。

我们使用F:F &lt;: Abst[F, T]的循环类型约束。虽然违反直觉,但编译器并不介意。

在实现中,MyClass,然后我们用Abst[MyClass, Param] 扩展MyClass,这反过来又满足F &lt;: Abst[F, T]

现在您可以在Abst 中使用F 作为函数的返回类型,并让MyClass 在实现中返回MyClass


你可能认为这个解决方案很丑,如果你这样做,那么你是对的。

始终建议使用 临时多态性的类型类,而不是使用 F 有界多态性

您将在我之前提供的链接中找到有关它的更多信息。

真的,阅读它。它将永远改变你对泛型编程的看法。

我希望这会有所帮助。

【讨论】:

  • 您好,感谢您的参考,很高兴看到不同的方法。你是对的,我认为它很丑
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-10
  • 2010-12-19
  • 2011-04-16
  • 2014-06-04
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多