在 Scala 中创建 self 类型的实例
Creating an instance of the self type in Scala
我不确定问题的标题是否用 Scala 类型术语表达正确...我面临的问题有点类似于 Scala collections 用 CanBuildFrom
管理的问题。
存在 higher-kinded 类型的层次结构和这些类型的容器(不一定 collections)的平行层次结构。例如,过滤容器中的项目的操作应该 return 执行操作的容器的确切类型,以及过滤的项目。
我遇到的问题是,如果不显式传递容器类型,我找不到将隐含证据要求限制为要构建的确切容器类型的正确方法。
下面是我正在处理的设置的简化版本。如果能就如何解决此问题提供一些建议,我将不胜感激。
@implicitNotFound(msg = "Cannot construct a ${B} from ${A} with arguments of type ${Args}.")
trait CanConstruct[A, -Args, +B] {
def apply(args: Args): B
}
class A1
trait C1[A <: A1] {
def a: A
def active: Boolean
}
trait C1s[C <: C1[A] forSome {type A <: A1}] {
def values: Seq[C]
// @note something like def active[From <: C1s[_]]... will find implicits
// but will be happy to use a super-type's implicit for a subtype's
def active[To[_ <: C]](implicit builder: CanConstruct[To[_], Seq[C], To[C]]): To[C] =
builder(values.filter(_.active))
}
class C2(val a: A1, val active: Boolean) extends C1[A1]
class C2s[C <: C2](val values: Seq[C]) extends C1s[C]
object C2s {
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[_], Seq[C], C2s[C]] {
def apply(args: Seq[C]): C2s[C] = new C2s(args)
}
implicit def C2CanConstruct[C <: C2] = new C2CanConstruct[C]
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values)
object C3s {
class C3CanConstruct[C <: C2] extends CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2] = new C3CanConstruct[C]
}
val a = new A1()
val cs = Seq(new C2(a, true), new C2(a, false))
// How can active pick the corresponding C?CanConstruct implicit automatically?
// i.e., new C2s(cs).active returns C2s[...] and
// new C3s(cs).active returns C3s[...]
new C2s(cs).active[C2s]
new C3s(cs).active[C3s]
更新:
下面答案中的类型成员方法很有前途,但无法处理 C1
具有类型参数 (Scastie) 的情况。我在最初的问题表述中省略了这个细节,希望在必要和不必要的复杂性之间取得平衡。当谈到类型成员方法时,似乎需要额外的复杂性。
编辑 2:Here's a fixed version of the Scastie 您 link 编辑了。问题在于 C1.T
/C2.T
的每次使用都是不同的,因此 C <: C1.T
不一定与 T <: C1.T
相同。上面的 link 与 Dmytro Mitin 的回答几乎相同,所以我没有把它放在这里。唯一不同的是它使用 type T = C1[_]
而不是 type T = C1[A] forSome {type A <: Config}
.
没有隐含的替代方案:
@annotation.implicitNotFound(
msg = "Cannot construct a ${To} from ${From} with arguments of type ${Args}."
)
trait CanConstruct[+From, -Args, +To] {
def apply(args: Args): To
}
class Config
trait C1[Cfg <: Config] {
type Config = Cfg
def config: Config
}
object C1 {
type T = C1[_]
}
trait C1s[C <: C1.T] {
type From <: C1s[_]
type To[T <: C] <: C1s[T]
def values: Seq[C]
protected def builder: CanConstruct[From, Seq[C], To[C]]
def filter(f: C => Boolean): To[C] =
builder(values.filter(f))
}
class C2[Cfg <: Config](val config: Cfg, val active: Boolean) extends C1[Cfg]
object C2 {
type T = C2[_]
}
abstract class C2s[C <: C2.T](val values: Seq[C]) extends C1s[C] {
type From <: C2s[_]
type To[T <: C] <: C2s[T]
}
object C2s {
def apply[C <: C2.T](values: Seq[C]) =
new C2s(values) {
type From = C2s[_]
type To[T2 <: C] = C2s[T2]
val builder = new CanConstruct[C2s[_], Seq[C], RefinedC2s[C]] {
def apply(args: Seq[C]) = C2s(args)
}
}
}
//Final because its type members are invariant
final class C3s[C <: C2.T](values: Seq[C]) extends C2s[C](values) {
type From = C3s[_]
type To[T <: C] = C3s[T]
protected val builder = new CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
}
val cfg = new Config
val cs = Seq(new C2(cfg, true), new C2(cfg, false))
val c2s = C2s(cs).filter(_.active).filter(_.active)
val c3s = new C3s(cs).filter(_.active).filter(_.active)
编辑:您还可以使 To
成为抽象类型成员。这里的问题是 To
在 C2
中必须是抽象的,这意味着将找不到隐含的。为了解决这个问题,我们可以创建一个像 type RefinedC2s[T <: C2] = C2s[T] { type To[T <: C2] = C2s[T] }
这样的精炼类型,并在你的伴生对象中创建一个 returns 的 apply
方法(你不必为 C3s
).您还必须使构造函数受到保护,以确保没有人使用抽象 To
.
创建 C2
trait C1 {
def active: Boolean
}
trait C1s[C <: C1] {
type To[T <: C] <: C1s[T]
//R is so that it can be RefinedC2s instead of C2s
def active[R <: To[C]](implicit builder: CanConstruct[To[_], Seq[C], R]): R =
builder(values.filter(_.active))
}
class C2s[C <: C2](val values: Seq[C]) extends C1s[C] {
type To[T <: C2] <: C2s[T]
}
object C2s {
type RefinedC2s[T <: C2] = C2s[T] { type To[T <: C2] = C2s[T] }
def apply[C <: C2](values: Seq[C]) = new C2s(values) { type To[T <: C2] = C2s[T] }
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[_], Seq[C], RefinedC2s[C]] {
def apply(args: Seq[C]) = C2s(args)
}
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values) {
type To[T <: C2] = C3s[T]
}
你可以这样使用它,不用担心每次都转换为 RefinedC2s
。
val c2s = C2s(cs).active.active.active.active.active
val c3s = new C3s(cs).active.active
考虑制作隐式 class 以添加 active
作为扩展方法。这比使用 To
作为类型参数(或作为我之前试验过的类型成员)
要容易得多
这是一个幼稚的尝试。我删除了 a
和 A1
,因为它们大多只是把示例弄得乱七八糟。我还在 CanBuildFrom
中使 A
协变,以便我可以传递它 C2s[Nothing]
,等等,因为我不知道它应该做什么。
import scala.language.existentials
@annotation.implicitNotFound(msg = "Cannot construct a ${B} from ${A} with arguments of type ${Args}.")
trait CanConstruct[+A, -Args, B] {
def apply(args: Args): B
}
trait C1 {
def active: Boolean
}
trait C1s[C <: C1] {
def values: Seq[C]
}
implicit class CsOps[C <: C1, Cs[_ <: C] <: C1s[_ <: C]](cs: Cs[C]) {
def active(implicit builder: CanConstruct[Cs[Nothing], Seq[C], Cs[C]]): Cs[C] =
builder(cs.values.filter(_.active))
}
class C2(val active: Boolean) extends C1
class C2s[C <: C2](val values: Seq[C]) extends C1s[C]
object C2s {
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[Nothing], Seq[C], C2s[C]] {
def apply(args: Seq[C]): C2s[C] = new C2s(args)
}
implicit def C2CanConstruct[C <: C2] = new C2CanConstruct[C]
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values)
object C3s {
class C3CanConstruct[C <: C2] extends CanConstruct[C3s[Nothing], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2] = new C3CanConstruct[C]
}
val cs = Seq(new C2(true), new C2(false))
val c2s: C2s[C2] = new C2s(cs).active
val c3s: C3s[C2] = new C3s(cs).active
c2s -> C2s(List(C2(true))): C2s[C2]
c3s -> C2s(List(C2(true))): C3s[C2]
关于您更新的问题(使用“真实世界”代码)尝试以下修改版本的 @user 的抽象类型成员 To
的第二种方法。我在 C1s
(必需)、C2s
(必需)、C3s
(可选)中修改了 To
参数的上限(否则覆盖 To
不会't work) 并使类型 class CanConstruct
相对于 To
协变(否则找不到隐式)。
@annotation.implicitNotFound(msg = "Cannot construct a ${To} from ${From} with arguments of type ${Args}.")
trait CanConstruct[+From, -Args, +To] {
def apply(args: Args): To
}
class Config
trait C1[Cfg <: Config] {
type Config = Cfg
def config: Config
}
object C1 {
type T = C1[A] forSome {type A <: Config}
}
trait C1s[C <: C1.T] {
type To[T <: /*C1.T*/C] <: C1s[T]
def values: Seq[C]
def filter[R <: To[C]](f: C => Boolean)(implicit builder: CanConstruct[To[_], Seq[C], R]): R =
builder(values.filter(f))
}
class C2[Cfg <: Config](val config: Cfg, val active: Boolean) extends C1[Cfg]
object C2 {
type T = C2[A] forSome {type A <: Config}
}
class C2s[C <: C2.T](val values: Seq[C]) extends C1s[C] {
type To[T <: /*C2.T*/C] <: C2s[T]
}
object C2s {
type RefinedC2s[C <: C2.T] = C2s[C] { type To[T <: /*C2.T*/C] = C2s[T] }
def apply[C <: C2.T](values: Seq[C]) = new C2s(values) { type To[T <: /*C2.T*/C] = C2s[T] }
class C2CanConstruct[C <: C2.T] extends CanConstruct[C2s[_], Seq[C], RefinedC2s[C]] {
def apply(args: Seq[C]) = C2s(args)
}
implicit def C2CanConstruct[C <: C2.T] = new C2CanConstruct[C]
}
class C3s[C <: C2.T](values: Seq[C]) extends C2s[C](values) {
type To[T <: /*C2.T*/C] = C3s[T]
}
object C3s {
class C3CanConstruct[C <: C2.T] extends CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2.T] = new C3CanConstruct[C]
}
val cfg = new Config
val cs = Seq(new C2(cfg, true), new C2(cfg, false))
implicitly[CanConstruct[C2s[_], Seq[C2[Config]], C2s[C2[Config]]]]
implicitly[CanConstruct[C3s[_], Seq[C2[Config]], C3s[C2[Config]]]]
val c2s = C2s(cs).filter(_ => true)
val c3s = new C3s(cs).filter(_ => true)
// val idontwork = new C2s(cs).filter(_ => true) //Doesn't work because `To` is abstract
c2s
c3s
https://scastie.scala-lang.org/JW9ZTNtnS1afRYvhBHqtWw
老实说,您的字体模型变得相当复杂。您应该重新考虑您是否真的需要所有这些东西(存在主义、泛型、更高种类、类型 classes、变体、类型...的混合)。
我不确定问题的标题是否用 Scala 类型术语表达正确...我面临的问题有点类似于 Scala collections 用 CanBuildFrom
管理的问题。
存在 higher-kinded 类型的层次结构和这些类型的容器(不一定 collections)的平行层次结构。例如,过滤容器中的项目的操作应该 return 执行操作的容器的确切类型,以及过滤的项目。
我遇到的问题是,如果不显式传递容器类型,我找不到将隐含证据要求限制为要构建的确切容器类型的正确方法。
下面是我正在处理的设置的简化版本。如果能就如何解决此问题提供一些建议,我将不胜感激。
@implicitNotFound(msg = "Cannot construct a ${B} from ${A} with arguments of type ${Args}.")
trait CanConstruct[A, -Args, +B] {
def apply(args: Args): B
}
class A1
trait C1[A <: A1] {
def a: A
def active: Boolean
}
trait C1s[C <: C1[A] forSome {type A <: A1}] {
def values: Seq[C]
// @note something like def active[From <: C1s[_]]... will find implicits
// but will be happy to use a super-type's implicit for a subtype's
def active[To[_ <: C]](implicit builder: CanConstruct[To[_], Seq[C], To[C]]): To[C] =
builder(values.filter(_.active))
}
class C2(val a: A1, val active: Boolean) extends C1[A1]
class C2s[C <: C2](val values: Seq[C]) extends C1s[C]
object C2s {
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[_], Seq[C], C2s[C]] {
def apply(args: Seq[C]): C2s[C] = new C2s(args)
}
implicit def C2CanConstruct[C <: C2] = new C2CanConstruct[C]
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values)
object C3s {
class C3CanConstruct[C <: C2] extends CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2] = new C3CanConstruct[C]
}
val a = new A1()
val cs = Seq(new C2(a, true), new C2(a, false))
// How can active pick the corresponding C?CanConstruct implicit automatically?
// i.e., new C2s(cs).active returns C2s[...] and
// new C3s(cs).active returns C3s[...]
new C2s(cs).active[C2s]
new C3s(cs).active[C3s]
更新:
下面答案中的类型成员方法很有前途,但无法处理 C1
具有类型参数 (Scastie) 的情况。我在最初的问题表述中省略了这个细节,希望在必要和不必要的复杂性之间取得平衡。当谈到类型成员方法时,似乎需要额外的复杂性。
编辑 2:Here's a fixed version of the Scastie 您 link 编辑了。问题在于 C1.T
/C2.T
的每次使用都是不同的,因此 C <: C1.T
不一定与 T <: C1.T
相同。上面的 link 与 Dmytro Mitin 的回答几乎相同,所以我没有把它放在这里。唯一不同的是它使用 type T = C1[_]
而不是 type T = C1[A] forSome {type A <: Config}
.
没有隐含的替代方案:
@annotation.implicitNotFound(
msg = "Cannot construct a ${To} from ${From} with arguments of type ${Args}."
)
trait CanConstruct[+From, -Args, +To] {
def apply(args: Args): To
}
class Config
trait C1[Cfg <: Config] {
type Config = Cfg
def config: Config
}
object C1 {
type T = C1[_]
}
trait C1s[C <: C1.T] {
type From <: C1s[_]
type To[T <: C] <: C1s[T]
def values: Seq[C]
protected def builder: CanConstruct[From, Seq[C], To[C]]
def filter(f: C => Boolean): To[C] =
builder(values.filter(f))
}
class C2[Cfg <: Config](val config: Cfg, val active: Boolean) extends C1[Cfg]
object C2 {
type T = C2[_]
}
abstract class C2s[C <: C2.T](val values: Seq[C]) extends C1s[C] {
type From <: C2s[_]
type To[T <: C] <: C2s[T]
}
object C2s {
def apply[C <: C2.T](values: Seq[C]) =
new C2s(values) {
type From = C2s[_]
type To[T2 <: C] = C2s[T2]
val builder = new CanConstruct[C2s[_], Seq[C], RefinedC2s[C]] {
def apply(args: Seq[C]) = C2s(args)
}
}
}
//Final because its type members are invariant
final class C3s[C <: C2.T](values: Seq[C]) extends C2s[C](values) {
type From = C3s[_]
type To[T <: C] = C3s[T]
protected val builder = new CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
}
val cfg = new Config
val cs = Seq(new C2(cfg, true), new C2(cfg, false))
val c2s = C2s(cs).filter(_.active).filter(_.active)
val c3s = new C3s(cs).filter(_.active).filter(_.active)
编辑:您还可以使 To
成为抽象类型成员。这里的问题是 To
在 C2
中必须是抽象的,这意味着将找不到隐含的。为了解决这个问题,我们可以创建一个像 type RefinedC2s[T <: C2] = C2s[T] { type To[T <: C2] = C2s[T] }
这样的精炼类型,并在你的伴生对象中创建一个 returns 的 apply
方法(你不必为 C3s
).您还必须使构造函数受到保护,以确保没有人使用抽象 To
.
C2
trait C1 {
def active: Boolean
}
trait C1s[C <: C1] {
type To[T <: C] <: C1s[T]
//R is so that it can be RefinedC2s instead of C2s
def active[R <: To[C]](implicit builder: CanConstruct[To[_], Seq[C], R]): R =
builder(values.filter(_.active))
}
class C2s[C <: C2](val values: Seq[C]) extends C1s[C] {
type To[T <: C2] <: C2s[T]
}
object C2s {
type RefinedC2s[T <: C2] = C2s[T] { type To[T <: C2] = C2s[T] }
def apply[C <: C2](values: Seq[C]) = new C2s(values) { type To[T <: C2] = C2s[T] }
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[_], Seq[C], RefinedC2s[C]] {
def apply(args: Seq[C]) = C2s(args)
}
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values) {
type To[T <: C2] = C3s[T]
}
你可以这样使用它,不用担心每次都转换为 RefinedC2s
。
val c2s = C2s(cs).active.active.active.active.active
val c3s = new C3s(cs).active.active
考虑制作隐式 class 以添加 active
作为扩展方法。这比使用 To
作为类型参数(或作为我之前试验过的类型成员)
这是一个幼稚的尝试。我删除了 a
和 A1
,因为它们大多只是把示例弄得乱七八糟。我还在 CanBuildFrom
中使 A
协变,以便我可以传递它 C2s[Nothing]
,等等,因为我不知道它应该做什么。
import scala.language.existentials
@annotation.implicitNotFound(msg = "Cannot construct a ${B} from ${A} with arguments of type ${Args}.")
trait CanConstruct[+A, -Args, B] {
def apply(args: Args): B
}
trait C1 {
def active: Boolean
}
trait C1s[C <: C1] {
def values: Seq[C]
}
implicit class CsOps[C <: C1, Cs[_ <: C] <: C1s[_ <: C]](cs: Cs[C]) {
def active(implicit builder: CanConstruct[Cs[Nothing], Seq[C], Cs[C]]): Cs[C] =
builder(cs.values.filter(_.active))
}
class C2(val active: Boolean) extends C1
class C2s[C <: C2](val values: Seq[C]) extends C1s[C]
object C2s {
class C2CanConstruct[C <: C2] extends CanConstruct[C2s[Nothing], Seq[C], C2s[C]] {
def apply(args: Seq[C]): C2s[C] = new C2s(args)
}
implicit def C2CanConstruct[C <: C2] = new C2CanConstruct[C]
}
class C3s[C <: C2](values: Seq[C]) extends C2s[C](values)
object C3s {
class C3CanConstruct[C <: C2] extends CanConstruct[C3s[Nothing], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2] = new C3CanConstruct[C]
}
val cs = Seq(new C2(true), new C2(false))
val c2s: C2s[C2] = new C2s(cs).active
val c3s: C3s[C2] = new C3s(cs).active
c2s -> C2s(List(C2(true))): C2s[C2]
c3s -> C2s(List(C2(true))): C3s[C2]
关于您更新的问题(使用“真实世界”代码)尝试以下修改版本的 @user 的抽象类型成员 To
的第二种方法。我在 C1s
(必需)、C2s
(必需)、C3s
(可选)中修改了 To
参数的上限(否则覆盖 To
不会't work) 并使类型 class CanConstruct
相对于 To
协变(否则找不到隐式)。
@annotation.implicitNotFound(msg = "Cannot construct a ${To} from ${From} with arguments of type ${Args}.")
trait CanConstruct[+From, -Args, +To] {
def apply(args: Args): To
}
class Config
trait C1[Cfg <: Config] {
type Config = Cfg
def config: Config
}
object C1 {
type T = C1[A] forSome {type A <: Config}
}
trait C1s[C <: C1.T] {
type To[T <: /*C1.T*/C] <: C1s[T]
def values: Seq[C]
def filter[R <: To[C]](f: C => Boolean)(implicit builder: CanConstruct[To[_], Seq[C], R]): R =
builder(values.filter(f))
}
class C2[Cfg <: Config](val config: Cfg, val active: Boolean) extends C1[Cfg]
object C2 {
type T = C2[A] forSome {type A <: Config}
}
class C2s[C <: C2.T](val values: Seq[C]) extends C1s[C] {
type To[T <: /*C2.T*/C] <: C2s[T]
}
object C2s {
type RefinedC2s[C <: C2.T] = C2s[C] { type To[T <: /*C2.T*/C] = C2s[T] }
def apply[C <: C2.T](values: Seq[C]) = new C2s(values) { type To[T <: /*C2.T*/C] = C2s[T] }
class C2CanConstruct[C <: C2.T] extends CanConstruct[C2s[_], Seq[C], RefinedC2s[C]] {
def apply(args: Seq[C]) = C2s(args)
}
implicit def C2CanConstruct[C <: C2.T] = new C2CanConstruct[C]
}
class C3s[C <: C2.T](values: Seq[C]) extends C2s[C](values) {
type To[T <: /*C2.T*/C] = C3s[T]
}
object C3s {
class C3CanConstruct[C <: C2.T] extends CanConstruct[C3s[_], Seq[C], C3s[C]] {
def apply(args: Seq[C]): C3s[C] = new C3s(args)
}
implicit def C3CanConstruct[C <: C2.T] = new C3CanConstruct[C]
}
val cfg = new Config
val cs = Seq(new C2(cfg, true), new C2(cfg, false))
implicitly[CanConstruct[C2s[_], Seq[C2[Config]], C2s[C2[Config]]]]
implicitly[CanConstruct[C3s[_], Seq[C2[Config]], C3s[C2[Config]]]]
val c2s = C2s(cs).filter(_ => true)
val c3s = new C3s(cs).filter(_ => true)
// val idontwork = new C2s(cs).filter(_ => true) //Doesn't work because `To` is abstract
c2s
c3s
https://scastie.scala-lang.org/JW9ZTNtnS1afRYvhBHqtWw
老实说,您的字体模型变得相当复杂。您应该重新考虑您是否真的需要所有这些东西(存在主义、泛型、更高种类、类型 classes、变体、类型...的混合)。