我有一个类似的情况:
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
我也尝试了一个具有泛型类型的解决方案,但后来我对此有冲突。T不同于v.T.
所以我只需要克服上面的错误信息,如果可能的话?
更新
因此,事实证明this.type
是该单个实例的singleton类型。我在一篇评论中建议使用
val s = met2(Param(1), (d: Double) => (new MyClass(d)).asInstanceOf[this.type])
所以,只要有人对此发表评论,我就知道它有多丑陋,只是感兴趣的是它有多不安全?
此外,你们都建议将Param的定义转移到课堂之外,我绝对同意。因此它的定义将在伴随对象MyClass 中
this.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: MyClass
的this.type
类型是一个非常糟糕的主意:
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)
尽管a
和b
都属于A
类型,但这会导致ClassCastException
爆炸。
如果您不介意让特性将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
使用超类型而不是子类型不会丢失任何信息。引用特征中的子类型的经典用例是,例如,有一个方法,即使在Abst
中定义了它,您也希望返回MyClass
而不是Abst
,但这不是您所处的情况。唯一使用子类型引用的地方是在f
的定义中,由于函数类型的输出参数是协变的,因此可以将任何f: Double => MyClass
传递到f: Double => Abst[T]
中,而不会出现问题。
如果您确实想引用子类型,请参阅Markus的答案。。。如果你真的想避免T
是一个通用参数,事情会再次变得复杂得多,因为现在Abst
的T
和met2
定义中的子类型的T
之间存在潜在的冲突。
要克服此错误消息,必须使用F-有界多态性。
你的代码看起来有点像这样:
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))
}
说明:
在特征或类定义中使用self: F =>
会约束this
的值。因此,如果this
的类型不是F
,您的代码将不会编译。
我们使用了一个循环类型约束F
:F <: Abst[F, T]
。尽管违反直觉,编译器并不介意。
在实现MyClass
中,我们用Abst[MyClass, Param]
扩展MyClass
,从而满足F <: Abst[F, T]
。
现在,您可以在Abst
中使用F
作为函数的返回类型,并在实现中让MyClass
返回MyClass
。
你可能认为这个解决方案很丑陋,如果你这样做了,那么你是对的。
不使用F-有界多态性,始终建议使用类型类来实现特定多态性。
你可以在我之前提供的链接中找到更多关于它的信息。
真的,读一读吧。它将永远改变你对泛型编程的看法。
我希望这能有所帮助。