具有约束的 Scala 泛型类型
Scala generic type with constraints
我正在研究 Scala 并想生成一些通用代码。我想要两个 class,一个 "outer" class 和一个 "inner" class。外部 class 应该是通用的,并接受任何类型的内部 class ,它遵循一些限制。这是我想要的那种架构,在不可编译的代码中。 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
都必须 return 一个 "updated" InType
对象。我还希望可以调用伴随方法,例如 InType.empty
。这样 Outer[InType]
和 InType
都是不可变类型,并且在伴生对象中定义的方法是可调用的。
前面的代码无法编译,因为它写得像 C++ 泛型类型(我的背景)。根据我提到的约束更正此代码的最简单方法是什么?我是不是完全错了,我应该使用另一种方法吗?
我能想到的一种方法是要求我们使用 F-Bounded Polymorphism along with 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
创建一个新实例。类型类模式是经典之作。我们创建一个构建 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
我们已经做到了。我知道一开始可能有点难以理解。阅读本文时,请随时提出更多问题。
我可以想到两种不涉及黑魔法的方法:
具有特征:
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]
)。
类型 class:
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
}
首先我们定义类型 class,然后我们隐含地要求它为我们的类型实现,最后我们提供并使用它。把整个理论的东西放在一边,实际的区别是这个接口是你不是强迫 InType
扩展一些 Updatable[InType]
,而是需要一些 Updatable[InType]
实现在你的范围 - 这样您就可以提供功能,而不是通过修改 InType
,而是通过提供一些额外的 class 来满足您的约束或 InType
.
由于这样的类型 class 更具可扩展性,您只需要为每个支持的类型提供隐式。
您可以使用的其他方法包括反射(但是这可能会破坏类型安全和重构能力)。
我正在研究 Scala 并想生成一些通用代码。我想要两个 class,一个 "outer" class 和一个 "inner" class。外部 class 应该是通用的,并接受任何类型的内部 class ,它遵循一些限制。这是我想要的那种架构,在不可编译的代码中。 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
都必须 return 一个 "updated" InType
对象。我还希望可以调用伴随方法,例如 InType.empty
。这样 Outer[InType]
和 InType
都是不可变类型,并且在伴生对象中定义的方法是可调用的。
前面的代码无法编译,因为它写得像 C++ 泛型类型(我的背景)。根据我提到的约束更正此代码的最简单方法是什么?我是不是完全错了,我应该使用另一种方法吗?
我能想到的一种方法是要求我们使用 F-Bounded Polymorphism along with 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
创建一个新实例。类型类模式是经典之作。我们创建一个构建 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
我们已经做到了。我知道一开始可能有点难以理解。阅读本文时,请随时提出更多问题。
我可以想到两种不涉及黑魔法的方法:
具有特征:
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]
)。类型 class:
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 }
首先我们定义类型 class,然后我们隐含地要求它为我们的类型实现,最后我们提供并使用它。把整个理论的东西放在一边,实际的区别是这个接口是你不是强迫
InType
扩展一些Updatable[InType]
,而是需要一些Updatable[InType]
实现在你的范围 - 这样您就可以提供功能,而不是通过修改InType
,而是通过提供一些额外的 class 来满足您的约束或InType
.
由于这样的类型 class 更具可扩展性,您只需要为每个支持的类型提供隐式。
您可以使用的其他方法包括反射(但是这可能会破坏类型安全和重构能力)。