在Scala中创建self-type的实例



我不确定问题的标题在Scala类型的术语中是否正确。。。我面临的问题与Scala集合使用CanBuildFrom管理的问题有些相似。

有一个更高级的kinded类型的层次结构和这些类型的容器(不一定是集合(的并行层次结构。比如说,一个过滤容器中项目的操作,应该返回执行该操作的容器的确切类型,并过滤项目。

我遇到的问题是,在不显式传递容器类型的情况下,我找不到将隐式证据要求限制为要构建的容器的确切类型的正确方法。

下面是我正在处理的设置的简化版本。我希望能就如何处理这个问题提供一些建议。

@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:这是您链接的Scastie的固定版本。问题是C1.T/C2.T的每次使用都不同,所以C <: C1.T不一定和T <: C1.T是一回事。上面的链接与德米特罗·米廷的答案几乎相同,所以我不把它放在这里。唯一不同的是它使用了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] }这样的精化类型,并在返回它的伴随对象中制作一个apply方法(对于C3s,您不必这样做(。您还必须保护构造函数,以确保没有人创建具有抽象ToC2

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


考虑创建一个隐式类来添加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

关于您更新的问题(使用"真实世界"代码(,请使用抽象类型成员To尝试以下@user第二种方法的修改版本。我修改了C1s(必需(、C2s(必需(和C3s(可选(中To参数的上界(否则To的重写不起作用(,并使类型类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

老实说,你的类型模型变得相当复杂。你应该重新考虑你是否真的需要所有这些东西(存在主义、泛型、更高种类、类型类、方差、类型…的混合物(

相关内容

  • 没有找到相关文章

最新更新