我正在使用Scala并希望生成一些通用代码。我想有两个班,一个是“外”班,一个是“内”班。外部类应该是通用的,并接受遵循一些约束的任何类型的内部类。这是我想要的架构,在无法编译的代码中。 Outer
是一种通用类型,Inner
是可以在Outer
中使用的类型示例。
class Outer[InType](val in: InType) {
def update: Outer[InType] = new Outer[InType](in.update)
def export: String = in.export
}
object Outer {
def init[InType]: Outer[InType] = new Outer[InType](InType.empty)
}
class Inner(val n: Int) {
def update: Inner = new Inner(n + 1)
def export: String = n.toString
}
object Inner {
def empty: Inner = new Inner(0)
}
object Main {
def main(args: Array[String]): Unit = {
val outerIn: Outer[Inner] = Outer.empty[Inner]
println(outerIn.update.export) // expected to print 1
}
}
重要的是,无论InType
是什么,in.update
都必须返回“已更新”的InType
对象。我也希望同伴方法可以调用,比如InType.empty
。这样,Outer[InType]
和InType
都是不可变类型,并且伴随对象中定义的方法是可调用的。
以前的代码无法编译,因为它的编写方式类似于C ++泛型类型(我的背景)。根据我提到的约束,更正此代码的最简单方法是什么?我完全错了,我应该使用另一种方法吗?
答案 0 :(得分:2)
我能想到的一种方法是要求我们使用F-Bounded Polymorphism和Type Classes。
首先,我们要创建一个需要update
方法可用的特征:
trait AbstractInner[T <: AbstractInner[T]] {
def update: T
def export: String
}
为Inner
:
class Inner(val n: Int) extends AbstractInner[Inner] {
def update: Inner = new Inner(n + 1)
def export: String = n.toString
}
要求Outer
仅采用扩展AbstractInner[InType]
的输入类型:
class Outer[InType <: AbstractInner[InType]](val in: InType) {
def update: Outer[InType] = new Outer[InType](in.update)
}
我们的类型适用于创建in
的更新版本,我们需要以某种方式使用empty
创建新实例。 Typeclass Pattern是经典之作。我们创建了一个构建Inner
类型的特征:
trait InnerBuilder[T <: AbstractInner[T]] {
def empty: T
}
我们要求Outer.empty
仅采用扩展AbstractInner[InType]
和范围内隐含InnerBuilder[InType]
的类型:
object Outer {
def empty[InType <: AbstractInner[InType] : InnerBuilder] =
new Outer(implicitly[InnerBuilder[InType]].empty)
}
并为Inner
提供具体实施:
object AbstractInnerImplicits {
implicit def innerBuilder: InnerBuilder[Inner] = new InnerBuilder[Inner] {
override def empty = new Inner(0)
}
}
在main中调用:
object Experiment {
import AbstractInnerImplicits._
def main(args: Array[String]): Unit = {
val outerIn: Outer[Inner] = Outer.empty[Inner]
println(outerIn.update.in.export)
}
}
收率:
1
我们有它。我知道一开始可能有点压倒性的。在阅读本文时,请随意提出更多问题。
答案 1 :(得分:0)
我可以想到两种不用黑魔法的方法:
有特质:
trait Updatable[T] { self: T =>
def update: T
}
class Outer[InType <: Updatable[InType]](val in: InType) {
def update = new Outer[InType](in.update)
}
class Inner(val n: Int) extends Updatable[Inner] {
def update = new Inner(n + 1)
}
首先我们使用trait,告诉类型系统update
方法是否可用,然后我们对类型设置限制以确保正确使用Updatable
(self: T =>
将确保它被用作T extends Updatable[T]
- 作为F-bounded类型),然后我们还确保InType将实现它(InType <: Updatable[InType]
)。
类型为:
trait Updatable[F] {
def update(value: F): F
}
class Outer[InType](val in: InType)(implicit updatable: Updatable[InType]) {
def update: Outer[InType] = new Outer[InType](updatable.update(in))
}
class Inner(val n: Int) {
def update: Inner = new Inner(n + 1)
}
implicit val updatableInner = new Updatable[Inner] {
def update(value: Inner): Inner = value.update
}
首先我们定义类型类,然后我们隐式地要求它为我们的类型实现,最后我们提供并使用它。抛开整个理论的东西,实际的不同之处在于,这个界面是你不强迫InType
扩展一些Updatable[InType]
,而是要求你有一些Updatable[InType]
实现在你的范围 - 因此您可以不是通过修改InType
来提供功能,而是通过提供一些可以满足您的约束或InType
的其他类。
由于此类类型更具可扩展性,因此您只需为每种受支持的类型提供隐式。
您可以使用的其他方法包括:反思(但这可能会破坏类型的安全性和你的重构能力)。