在 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)

Scastie


编辑:您还可以使 To 成为抽象类型成员。这里的问题是 ToC2 中必须是抽象的,这意味着将找不到隐含的。为了解决这个问题,我们可以创建一个像 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

Scastie


考虑制作隐式 class 以添加 active 作为扩展方法。这比使用 To 作为类型参数(或作为我之前试验过的类型成员)

要容易得多

这是一个幼稚的尝试。我删除了 aA1,因为它们大多只是把示例弄得乱七八糟。我还在 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]

Scastie

关于您更新的问题(使用“真实世界”代码)尝试以下修改版本的 @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、变体、类型...的混合)。