如何使两个不同特征的依赖类型被识别为同一类型

How to make it so that dependent types in two different traits are recognized as the same type

我 运行 遇到一个问题,我正在使用多个使用依赖类型的特征,但是当我尝试在我的业务逻辑中组合这些特征时,出现编译错误。

import java.util.UUID

object TestDependentTypes extends App{
  val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))

  RealConfLoader(7).extractData(myConf.settings)

}

trait Data

case class RealData(anInt: Int, aDouble: Double) extends Data

trait MySettings

case class RealSettings(data: RealData) extends MySettings

trait Conf {
  type T <: MySettings
  def id: UUID
  def settings: T
}

case class RealConf(id: UUID, settings: RealSettings) extends Conf {
  type T = RealSettings
}

trait ConfLoader{
  type T <: MySettings
  type U <: Data
  def extractData(settings: T): U
}

case class RealConfLoader(someInfo: Int) extends ConfLoader {
  type T = RealSettings
  type U = RealData
  override def extractData(settings: RealSettings): RealData = settings.data
}

processor 中的代码将无法编译,因为 extractData 需要 ConfLoader.T 类型的输入,但 conf.settingsConf.T 类型的输入。这些是不同的类型。

但是,我已经指定两者都必须是 MySettings 的子类,所以我应该可以在需要另一个的地方使用一个。我知道 Scala 不编译代码,但是有一些解决方法可以将 conf.settings 传递给 confLoader.extractData?

===

我想报告一下,对于我上面编写的代码,有一种编写它的方法可以减少我对依赖类型的使用。我今天在试验 Traits 时注意到,Scala 支持对实现 Trait 的 类 上的 defs 和 vals 进行子类化。所以我只需要为 extractData 的参数创建一个依赖类型,而不是输出。

import java.util.UUID

object TestDependentTypes extends App{
  val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))

  RealConfLoader(7).extractData(myConf.settings)

  def processor(confLoader: ConfLoader, conf: Conf) = confLoader.extractData(conf.settings.asInstanceOf[confLoader.T])
}

trait Data

case class RealData(anInt: Int, aDouble: Double) extends Data

trait MySettings

case class RealSettings(data: RealData) extends MySettings

trait Conf {
  def id: UUID
  def settings: MySettings
}

case class RealConf(id: UUID, settings: RealSettings) extends Conf

trait ConfLoader{
  type T <: MySettings
  def extractData(settings: T): Data
}

case class RealConfLoader(someInfo: Int) extends ConfLoader {
  type T = RealSettings
  override def extractData(settings: RealSettings): RealData = settings.data
}

上面的代码做了同样的事情,减少了对依赖类型的依赖。我只从代码中删除了 processor。 processor的实现可以参考下面任意一个方案。

The code in processor will not compile because extractData expects input of type ConfLoader.T, but conf.settings is of type Conf.T. Those are different types.

在方法processor中你应该指定这些类型是相同的。

为此使用类型优化 (1, 2):

def processor[_T](confLoader: ConfLoader { type T = _T }, conf: Conf { type T = _T }) = 
  confLoader.extractData(conf.settings)

def processor(confLoader: ConfLoader)(conf: Conf { type T = confLoader.T }) = 
  confLoader.extractData(conf.settings)

def processor(conf: Conf)(confLoader: ConfLoader { type T = conf.T }) = 
  confLoader.extractData(conf.settings)

恕我直言,如果您不需要依赖类型提供的任何功能,您应该只使用普通的类型参数.

因此:

trait Conf[S <: MySettings] {
  def id: UUID
  def settings: S
}

final case class RealConf(id: UUID, settings: RealSettings) extends Conf[RealSettings]

trait ConfLoader[S <: MySettings, D <: Data] {
  def extractData(settings: S): D
}

final case class RealConfLoader(someInfo: Int) extends ConfLoader[RealSettings, RealData] {
  override def extractData(settings: RealSettings): RealData =
    settings.data
}

def processor[S <: MySettings, D <: Data](loader: ConfLoader[S, D])(conf: Conf[S]): D =
  loader.extractData(conf.settings)

但是,如果您确实需要它们是 类型的成员,您可以确保两者相同。

def processor(loader: ConfLoader)(conf: Conf)
             (implicit ev: conf.S <:< loader.S): loader.D =
  loader.extractData(conf.settings)