scala类型类扩展/泛化初始化
我遇到了一个奇怪而困惑的NPE。考虑下面的用例:scala类型类扩展/泛化初始化,scala,nullpointerexception,typeclass,Scala,Nullpointerexception,Typeclass,我遇到了一个奇怪而困惑的NPE。考虑下面的用例: 编写一个泛型算法(在我的例子中是二进制搜索),其中您希望泛化类型,但需要一些额外的东西 e、 g:也许你想把一个范围一分为二,你需要一个通用的half或two常量 Integraltypeclass是不够的,因为它只提供one和zero,所以我想到了: trait IntegralConsts[N] { val tc: Integral[N] val two = tc.plus(tc.one,tc.one) val four = tc
编写一个泛型算法(在我的例子中是二进制搜索),其中您希望泛化类型,但需要一些额外的东西 e、 g:也许你想把一个范围一分为二,你需要一个通用的
half
或two
常量
Integral
typeclass是不够的,因为它只提供one
和zero
,所以我想到了:
trait IntegralConsts[N] {
val tc: Integral[N]
val two = tc.plus(tc.one,tc.one)
val four = tc.plus(two,two)
}
object IntegralConsts {
implicit def consts[N : Integral] = new IntegralConsts[N] {
override val tc = implicitly[Integral[N]]
}
}
并按如下方式使用:
def binRangeSearch[N : IntegralConsts]( /* irrelevant args */ ) = {
val consts = implicitly[IntegralConsts[N]]
val math = consts.tc
// some irrelevant logic, which contain expressions like:
val halfRange = math.quot(range, consts.two)
// ...
}
在运行时,这会在这一行抛出一个令人费解的NullPointerException
:valtwo=tc.plus(tc.one,tc.one)
作为一种解决方法,我刚刚在typeclass的
val
s中添加了lazy
,这一切都解决了:
trait IntegralConsts[N] {
val tc: Integral[N]
lazy val two = tc.plus(tc.one,tc.one)
lazy val four = tc.plus(two,two)
}
但我想知道为什么我会有这种奇怪的NPE。初始化顺序应该是已知的,tc
应该在到达valtwo…
初始化顺序应该是已知的,tc
应该已经
到达val two时实例化
不符合规格。真正发生的是,在构造匿名类时,首先初始化IntegralConsts[T]
,然后才在派生的anon类中取消对tc
的重写,这就是您遇到NullPointerException
的原因
报告说:
模板评估
考虑一个带有mtn{stats}的mt1的模板sc
如果这是trait的模板,那么它的mixin计算包括语句序列stats的计算
如果这不是特征的模板,则其评估包括以下步骤:
- 首先,对超类构造函数
sc
进行评估
- 然后,对模板线性化中的所有基类进行混合求值,直至模板的超类(由
sc
表示)。在线性化过程中,Mixin评估的发生顺序与发生顺序相反
- 最后计算语句序列
stats
我们可以通过使用-Xprint:typer
查看编译后的代码来验证这一点:
final class $anon extends AnyRef with IntegralConsts[N] {
def <init>(): <$anon: IntegralConsts[N]> = {
$anon.super.<init>();
()
};
private[this] val tc: Integral[N] = scala.Predef.implicitly[Integral[N]](evidence$1);
override <stable> <accessor> def tc: Integral[N] = $anon.this.tc
};
正如您所注意到的,由于这是一个匿名类,因此将惰性
添加到定义中可以完全避免初始化问题。另一种方法是使用早期定义:
object IntegralConsts {
implicit def consts[N : Integral] = new {
override val tc = implicitly[Integral[N]]
} with IntegralConsts[N]
}
回答得好!谢谢出于某种原因,我的印象是应该急切地实例化抽象的val
。另一个选项是将tc
声明为抽象的def
,并在匿名类中覆盖lazy val
(因为lazy val
s本质上是def
s…)@giladhoch很高兴它有帮助!初始化部分可能会有点棘手。我认为我不太喜欢的一点是,所有这些变通方法都依赖于约定,因为其他开发人员可能会被约定所困扰。
object IntegralConsts {
implicit def consts[N : Integral] = new {
override val tc = implicitly[Integral[N]]
} with IntegralConsts[N]
}