从特征扩展,其中每个实例的单例类型是类型参数

时间:2015-08-14 08:08:32

标签: scala inheritance types singleton-type

假设我希望Item的每个实例都是SourceOf Item,而SourceOf的每个Item子类型都是SourceOf 1}}该子类型的所有实例。这似乎是一种自然的方式:

object Demo {
  trait Item extends SourceOf[this.type]

  trait SourceOf[+A <: Item]
}

显然这不起作用,因为this引用Demo对象,而不引用每个Item实例。 (我已经测试过了。)

如何告诉Scala编译器每个Item都是自己的源?应该可以让编译器推导出类似的东西:

  • “(所有)农具的每一个来源都是您正在寻找的特定农具的来源。”

  • “如果您正在寻找农具的来源,并且您已经拥有农具,那么您不需要再看了。”

这是一个例子(为简洁起见忽略类型擦除):

trait FarmingImplement extends Item
object TheRototiller extends FarmingImplement

def findSource(item: Item, seq: Seq[SourceOf[_]]): Option[SourceOf[item.type]] =
  seq.collectFirst {
    case src: SourceOf[item.type] => src
  }

val sources = Seq(   // example: possible sources of TheRototiller
  new SourceOf[Nothing] { override def toString = "Wrong source" },
  new SourceOf[FarmingImplement] { override def toString = "Right" },
  TheRototiller /* also right */ )

val got = findSource(TheRototiller, sources).get
println(got)  // Should print "Right", since the second source in `sources`
              // is the first match.

我希望got的类型为SourceOf[TheRototiller.type],而不是SourceOf[Item]。但主要是我希望Scala的类型系统能够确定SourceOf是否匹配给定ItemItems类别。

1 个答案:

答案 0 :(得分:1)

至少,这将通过传递this来解决您的问题:

object Demo {

  trait C[+A]

  trait Item extends SourceOf {
    type T = C[this.type]
  }

  trait SourceOf {
    type T <: C[Item]
  }
}

示例(您必须在此处比较T而不是SourceOf):

scala> val i = new Item{}
i: Demo.Item = $anon$1@c70cb4c

scala> implicitly[i.T =:= i.T] //compile-time type equality check
res4: =:=[i.T,i.T] = <function1>

scala> val i2 = new Item{}
i2: Demo.Item = $anon$1@1f26ac06

scala> implicitly[i.T =:= i2.T]
<console>:18: error: Cannot prove that i.T =:= i2.T.
              implicitly[i.T =:= i2.T]

如果你需要在运行时检查类型(正如你在模式匹配的示例中所示) - 我建议使用TypeTag

import scala.reflect.runtime.universe._

object Demo {

  abstract class C[+A: TypeTag] 

  trait Item extends SourceOf {
    type T = C[this.type]
  }

  trait SourceOf {
    val tag = typeTag[this.type] //just to access
  }
}

用法:

scala> val i = new Item{}
i: Demo.Item = $anon$1@3b2111b0

scala> val i2 = new Item{}
i2: Demo.Item = $anon$1@4e7bb48a

scala> typeTag[i.T].tpe =:= typeTag[i2.T].tpe //runtime type-equality check
res9: Boolean = false

scala> typeTag[i.T].tpe =:= typeTag[i.T].tpe
res10: Boolean = true

最后,实施findSource

import scala.reflect.runtime.universe._

object Demo {

      trait Item extends SourceOf 

      trait SourceOf {
        type I = this.type
        val tag = typeTag[I]
      }

      def findSource(item: Item, seq: Seq[SourceOf]): Option[item.I] =
        seq.find(_.tag.tpe =:= item.tag.tpe).asInstanceOf[Option[item.I]]
}

示例:

scala> val i = new Item{}
i: Demo.Item = $anon$1@1fc2899d

scala> val i2 = new Item{}
i2: Demo.Item = $anon$1@5f308abb

scala> val ri = findSource(i, Seq(i, i2)).get
ri: i.I = $anon$1@1fc2899d

scala> implicitly[ri.I =:= i.I]
res2: =:=[ri.I,i.I] = <function1>
使用

asInstanceOf(这里安全可行),因为Seq在编译时失去了路径依赖类型,我们使用运行时检查来匹配类型,所以没办法找到它的编译器。但是,Shapeless2的基于HList + Selector的实现可能更纯粹。