Return 后的大括号键入函数定义

Curly Parentheses After Return Type in Function Definition

当阅读cats library's Functor source时,我无法理解函数toFunctorOps的return类型后面的卷曲块是做什么的;我的猜测是这个块将作为构造函数的一部分执行?如果是这样,那为什么 type TypeClassType 用相同的代码定义了两次 type TypeClassType = Functor[F]?

  trait Ops[F[_], A] extends Serializable {
    type TypeClassType <: Functor[F]
    def self: F[A]
    val typeClassInstance: TypeClassType
    def map[B](f: A => B): F[B] = typeClassInstance.map[A, B](self)(f)
    ...
  }

  trait ToFunctorOps extends Serializable {
    implicit def toFunctorOps[F[_], A](target: F[A])(implicit tc: Functor[F]): Ops[F, A] {
      type TypeClassType = Functor[F]
    } =
      new Ops[F, A] {
        type TypeClassType = Functor[F]
        val self: F[A] = target
        val typeClassInstance: TypeClassType = tc
      }
  }

I could not understand what the curly block after the return type ... does

细化 { type TypeClassType = Functor[F] } 进一步限制了特征 Ops 的类型成员 TypeClassType。换句话说,它向编译器提供了有关特定 return 类型方法的更多信息 toFunctorOps

Ops[F, A] { type TypeClassType = Functor[F] }

请注意,细化块被视为 return 类型的 部分 ,与构造函数无关。

让我们简化类型以更好地说明概念,因此请考虑

trait Foo {
  type A
  def bar: A
}

val foo: Foo = new Foo { 
  type A = Int 
  def bar: A = ???
}
val x: foo.A = 42 // type mismatch error

请注意变量 foo 的静态类型如何不包含类型成员 A 已实例化为 Int 的特定信息。现在让我们使用类型细化

向编译器提供此信息
val foo: Foo { type A = Int } = new Foo { 
  type A = Int
  def bar: A = ??? 
}
val x: foo.A = 42 // ok

现在编译器知道类型成员 A 恰好是 Int.

类型 类 的设计者在何时使用类型成员而不是类型参数方面做出明智的决定,有时甚至会像您的情况那样混合使用两者。例如 trait Foo 可以像这样被参数化

trait Foo[A] {
  def bar: A
}
val foo: Foo[Int] = new Foo[Int] { 
  def bar: Int = ??? 
}

并且编译器将再次获得类型参数 A 已实例化为 Int.

的准确信息

why type TypeClassType is defined twice

精化类型 Foo { type A = Int }Foo 的更窄子类型,类似于 CatAnimal

的更窄子类型
implicitly[(Foo { type A = Int }) <:< Foo]
implicitly[Cat <:< Animal]

所以即使右侧表达式将 A 实例化为 Int,左侧表达式明确告诉编译器 foo 的静态类型只是更宽的超类型 Foo

val foo: Foo = new Foo { 
  type A = Int 
  def bar: A = ???
}

类似于编译器如何知道下面 zar 的静态类型只是更广泛的超类型 Animal,尽管 RHS 上的表达式指定了 Cat

val zar: Animal = new Cat

因此需要“double”类型规范

val foo: Foo { type A = Int } = new Foo { 
  type A = Int 
  ...
}

类似于

val zar: Cat = new Cat

我们可以尝试依靠推理来推断最具体的类型,但是当我们显式注释类型时,我们必须提供完整的信息,包括通过细化的类型成员约束。