Scala 3 — 书籍

依赖函数类型

语言
此文档页面特定于 Scala 3,并且可能涵盖 Scala 2 中不可用的新概念。除非另有说明,否则此页面中的所有代码示例均假定你使用的是 Scala 3。

依赖函数类型描述函数类型,其中结果类型可能取决于函数的参数值。依赖类型和依赖函数类型是一个更高级的概念,你通常只有在设计自己的库或使用高级库时才会遇到它。

依赖方法类型

让我们考虑以下异构数据库的示例,该数据库可以存储不同类型的值。键包含有关相应值类型的信息

trait Key { type Value }

trait DB {
  def get(k: Key): Option[k.Value] // a dependent method
}

给定一个键,方法 get 让我们访问映射,并可能返回类型为 k.Value 的存储值。我们可以将此路径依赖类型解读为:“根据参数 k 的具体类型,我们返回一个匹配的值”。

例如,我们可以有以下键

object Name extends Key { type Value = String }
object Age extends Key { type Value = Int }

现在,对方法 get 的以下调用将进行类型检查

val db: DB = ...
val res1: Option[String] = db.get(Name)
val res2: Option[Int] = db.get(Age)

调用方法 db.get(Name) 会返回类型为 Option[String] 的值,而调用 db.get(Age) 会返回类型为 Option[Int] 的值。返回类型取决于传递给 get 的参数的具体类型——因此得名依赖类型

依赖函数类型

如上所述,Scala 2 已经支持依赖方法类型。但是,创建类型为 DB 的值非常麻烦

// a user of a DB
def user(db: DB): Unit =
  db.get(Name) ... db.get(Age)

// creating an instance of the DB and passing it to `user`
user(new DB {
  def get(k: Key): Option[k.Value] = ... // implementation of DB
})

我们需要手动创建一个 DB 的匿名内部类,实现 get 方法。对于依赖于创建 DB 的许多不同实例的代码来说,这是非常繁琐的。

特质 DB 只有一个抽象方法 get。如果我们可以改用 lambda 语法,那不是很好吗?

user { k =>
  ... // implementation of DB
}

事实上,这现在在 Scala 3 中是可能的!我们可以将 DB 定义为依赖函数类型

type DB = (k: Key) => Option[k.Value]
//        ^^^^^^^^^^^^^^^^^^^^^^^^^^^
//      A dependent function type

给定这个 DB 的定义,对 user 的上述调用会按原样进行类型检查。

您可以在参考文档中阅读有关依赖函数类型的内部机制的更多信息。

案例研究:数值表达式

让我们假设我们要定义一个对数字的内部表示进行抽象的模块。例如,这对于实现自动派生的库很有用。

我们从定义数字模块开始

trait Nums:
  // the type of numbers is left abstract
  type Num

  // some operations on numbers
  def lit(d: Double): Num
  def add(l: Num, r: Num): Num
  def mul(l: Num, r: Num): Num

我们省略了 Nums 的具体实现,但作为练习,您可以通过将 type Num = Double 分配给 Nums 并相应地实现方法来实现 Nums

现在使用我们的数字抽象的程序具有以下类型

type Prog = (n: Nums) => n.Num => n.Num

val ex: Prog = nums => x => nums.add(nums.lit(0.8), x)

计算 ex 等程序的导数的函数的类型是

def derivative(input: Prog): Double

借助依赖函数类型的便利性,使用不同的程序调用此函数非常方便

derivative { nums => x => x }
derivative { nums => x => nums.add(nums.lit(0.8), x) }
// ...

回想一下,上述编码中的相同程序将是

derivative(new Prog {
  def apply(nums: Nums)(x: nums.Num): nums.Num = x
})
derivative(new Prog {
  def apply(nums: Nums)(x: nums.Num): nums.Num = nums.add(nums.lit(0.8), x)
})
// ...

与上下文函数结合

扩展方法、上下文函数和依赖函数的组合为库设计人员提供了一个强大的工具。例如,我们可以按如下方式改进我们上面的库

trait NumsDSL extends Nums:
  extension (x: Num)
    def +(y: Num) = add(x, y)
    def *(y: Num) = mul(x, y)

def const(d: Double)(using n: Nums): n.Num = n.lit(d)

type Prog = (n: NumsDSL) ?=> n.Num => n.Num
//                       ^^^
//     prog is now a context function that implicitly
//     assumes a NumsDSL in the calling context

def derivative(input: Prog): Double = ...

// notice how we do not need to mention Nums in the examples below?
derivative { x => const(1.0) + x }
derivative { x => x * x + const(2.0) }
// ...

此页面贡献者