在对象中没有定义的类型声明是什么意思?

时间:2019-01-10 21:59:29

标签: scala types implicit abstract-type

Scala允许使用type关键字定义类型,这些类型通常具有不同的含义和目的,具体取决于声明的时间。

如果在对象或包对象中使用type,则需要定义类型别名,即另一种类型的简称/简称:

package object whatever {
  type IntPredicate = Int => Boolean

  def checkZero(p: IntPredicate): Boolean = p(0)
}

在类/特征中声明的类型通常旨在在子类/子特征中被覆盖,并最终也解析为具体类型:

trait FixtureSpec {
  type FixtureType
  def initFixture(f: FixtureType) = ...
}

trait SomeSpec extends FixtureSpec {
  override type FixtureType = String

  def test(): Unit = {
    initFixture("hello")
    ...
  }
}

抽象类型声明还有其他用途,但无论如何最终它们都会解析为某些具体类型。

但是,还有一个选项可以在 object 内声明抽象类型(即没有实际定义):

object Example {
  type X
}

这会编译,而不是像抽象方法:

object Example {
  def method: String  // compilation error
}

由于无法扩展对象,因此无法将其解析为具体类型。

我假设这样的类型定义可以方便地用作幻像类型。例如(使用Shapeless的标记类型):

import shapeless.tag.@@
import shapeless.tag

type ++>[-F, +T]

trait Converter

val intStringConverter: Converter @@ (String ++> Int) = tag[String ++> Int](...)

但是,似乎类型系统对待这些类型的方式与常规类型不同,这导致上述“抽象”类型的使用在某些情况下失败。

尤其是,当寻找隐式参数时,Scala最终将查找与“关联”类型相关联的隐式范围,即,隐式参数的类型签名中存在的类型。但是,当使用“抽象”类型时,这些关联类型的嵌套似乎存在一些限制。考虑以下示例设置:

import shapeless.tag.@@

trait Converter

type ++>[-F, +T]

case class DomainType()

object DomainType {
  implicit val converter0: Converter @@ DomainType = null
  implicit val converter1: Converter @@ Seq[DomainType] = null
  implicit val converter2: Converter @@ (Seq[String] ++> Seq[DomainType]) = null

}

// compiles
implicitly[Converter @@ DomainType]
// compiles
implicitly[Converter @@ Seq[DomainType]]
// fails!
implicitly[Converter @@ (Seq[String] ++> Seq[DomainType])]

在这里,前两个隐式解决方案可以很好地编译,而最后一个隐式解决方案失败,并出现有关缺少的隐式解决方案的错误。如果我在与implicitly调用相同的作用域中定义隐式函数,则它将编译:

implicit val converter2: Converter @@ (Seq[String] ++> Seq[DomainType]) = null
// compiles
implicitly[Converter @@ (Seq[String] ++> Seq[DomainType])]

但是,如果我将++>的定义改为trait而不是type

trait ++>[-F, +T]

然后上面的所有implicitly调用都可以编译。

因此,我的问题是,此类类型声明的确切目的是什么?它们打算解决什么问题?为什么不像对象中的其他抽象成员一样禁止使用它们?

1 个答案:

答案 0 :(得分:4)

对于一个方法(或值),只有2个选项:它具有主体(然后为“具体”)或没有主体(则为“抽象”)。类型X始终是某个类型间隔X >: LowerBound <: UpperBound(如果将LowerBound = UpperBound称为具体间隔,或者将LowerBound = NothingUpperBound = Any称为完全抽象,但情况多种多样之间)。因此,如果我们要禁止对象中使用抽象类型,则应该始终有办法检查LowerBoundUpperBound类型是否相等。但是可以用一些复杂的方式定义它们,通常这种检查并不容易:

object Example {
  type X >: N#Add[N] <: N#Mult[Two] // Do we expect that compiler proves n+n=n*2?
}