依赖类型可以在编译时消除更多的logic errors。为了说明这一点,请考虑以下关于函数 f 的规范:
函数f 必须只接受偶数 个整数作为输入。
如果没有依赖类型,您可能会执行以下操作:
def f(n: Integer) := {
if n mod 2 != 0 then
throw RuntimeException
else
// do something with n
}
这里编译器无法检测n是否确实是偶数,也就是说,从编译器的角度来看,下面的表达式是可以的:
f(1) // compiles OK despite being a logic error!
这个程序会运行,然后在运行时抛出异常,即你的程序有逻辑错误。
现在,依赖类型可以让您更有表现力,并且可以让您编写如下内容:
def f(n: {n: Integer | n mod 2 == 0}) := {
// do something with n
}
这里的n 是依赖类型{n: Integer | n mod 2 == 0}。大声读出来可能会有所帮助
n 是一组整数的成员,因此每个整数都可以被
2.
在这种情况下,编译器会在编译时检测到您将奇数传递给 f 的逻辑错误,并会阻止程序首先执行:
f(1) // compiler error
这是一个使用 Scala path-dependent types 的说明性示例,说明我们如何尝试实现满足此类要求的函数 f:
case class Integer(v: Int) {
object IsEven { require(v % 2 == 0) }
object IsOdd { require(v % 2 != 0) }
}
def f(n: Integer)(implicit proof: n.IsEven.type) = {
// do something with n safe in the knowledge it is even
}
val `42` = Integer(42)
implicit val proof42IsEven = `42`.IsEven
val `1` = Integer(1)
implicit val proof1IsOdd = `1`.IsOdd
f(`42`) // OK
f(`1`) // compile-time error
关键是注意值n如何出现在值proof的类型中,即n.IsEven.type:
def f(n: Integer)(implicit proof: n.IsEven.type)
^ ^
| |
value value
我们说 type n.IsEven.type 取决于 value n 因此术语 dependent-types。
作为另一个示例,让我们定义一个 dependently typed function,其中返回类型将取决于 value 参数。
使用 Scala 3 工具,请考虑以下 heterogeneous list,它维护其每个元素的精确类型(而不是推导出常见的最小上限)
val hlist: (Int, List[Int], String) = 42 *: List(42) *: "foo" *: Tuple()
目标是索引不应该丢失任何编译时类型信息,例如,在索引第三个元素之后,编译器应该知道它正是String
val i: Int = index(hlist)(0) // type Int depends on value 0
val l: List[Int] = index(hlist)(1) // type List[Int] depends on value 1
val s: String = index(hlist)(2) // type String depends on value 2
这是依赖类型函数index的签名
type DTF = [L <: Tuple] => L => (idx: Int) => Elem[L, idx.type]
| |
value return type depends on value
或者换句话说
对于L 类型的所有异构列表,以及所有(值)索引
idx 类型为Int,返回类型为Elem[L, idx.type]
我们再次注意到返回类型如何取决于值idx。
这里是完整的实现供参考,它利用了literal-based singleton types、Peano 在类型级别的整数实现、match types 和dependent functions types,但是关于这个 Scala 特定实现的确切细节对于这个答案来说,作品并不重要,这只是试图说明关于依赖类型的两个关键概念
- 类型可以依赖于值
- 允许在编译时消除更广泛的错误
// Bring in scope Peano numbers which are integers lifted to type-level
import compiletime.ops.int._
// Match type which reduces to the exact type of an HList element at index IDX
type Elem[L <: Tuple, IDX <: Int] = L match {
case head *: tail =>
IDX match {
case 0 => head
case S[nextIdx] => Elem[tail, nextIdx]
}
}
// type of dependently typed function index
type DTF = [L <: Tuple] => L => (idx: Int) => Elem[L, idx.type]
// implementation of DTF index
val index: DTF = [L <: Tuple] => (hlist: L) => (idx: Int) => {
hlist.productElement(idx).asInstanceOf[Elem[L, idx.type]]
}
给定依赖类型 DFT 编译器现在能够在编译时捕获以下错误
val a: String = (42 :: "foo" :: Nil)(0).asInstanceOf[String] // run-time error
val b: String = index(42 *: "foo" *: Tuple())(0) // compile-time error
scastie