Scala:由嵌套类型值参数化的类型级编程

Mat*_*oob 5 scala type-constraints type-level-computation

目前我有一个实现peano算法的程序:

sealed trait NaturalNumber
Run Code Online (Sandbox Code Playgroud)

和一个调用的函数getResource在其参数中选择具有接口的自然数字类型值:

sealed trait VersionNumber {
  type Nat <: NaturalNumber
}
Run Code Online (Sandbox Code Playgroud)

并根据引用类型 - 值版本号检查值:MAJMIN在此接口中提供:

trait ResourceManifest {
  def getResource: Int
  type Major <: NaturalNumber
  type Minor <: NaturalNumber
}
Run Code Online (Sandbox Code Playgroud)

取决于哪个,函数执行或不编译.该函数具有以下形式:

    def getResource(manifest: ResourceManifest)(maj: VersionNumber, min: VersionNumber)
               (implicit
                maj_check: manifest.Major IsEqual maj.Nat,
                min_check: manifest.Minor IsLessOrEqual min.Nat
) = manifest.getResource
Run Code Online (Sandbox Code Playgroud)

这是完整的代码.(如果你喜欢类型递归,这是一个替代实现.)

实际上,这是由覆盖的类型值驱动的,普通的Scala用户可能不太习惯.此外,getResource获取主要版本和次要版本的单独参数.

理想情况下,我想用户在包装类中提供值而不是类型:

case class VersionInfo(major: VersionNumber, minor: VersionNumber)
Run Code Online (Sandbox Code Playgroud)

所以我的清单是这样的形式:

trait ResourceManifestRefactored {
  def getResource: Int
  val versionInfo: VersionInfo
}
Run Code Online (Sandbox Code Playgroud)

同样有:

def getResourceRefactored(manifest: ResourceManifestRefactored)(versionInfo: VersionInfo)
Run Code Online (Sandbox Code Playgroud)

并通过从包装器版本值类中获取版本类型来执行我的类型级别约束:VersionInfo.然而,尽管我以许多不同的方式做到这一点,但我仍在努力让它发挥作用.例如,我尝试直接使用路径依赖类型进行类型检查,但失败了.我也尝试定义MAJMIN基于内部类型,VersionInfo但类型约束不再按照预期的方式工作.我知道我们可能面临类似问题,这些问题可以通过aux-pattern来解决,但我正在努力为我的问题提供类似的解决方案.

本质上,我想要预先定义的对象包装类型,我想通过这些对象而不是直接类型来进行类型约束.

是否有一个基本的原因,我不能,如果没有,我怎么能这样做?

And*_*kin 2

假设__1__2是两个扩展的对象VersionNumber,并且在它们内部_1, _2扩展了两种不同的类型Nat。有什么原因导致编译器拒绝编译

val foo: VersionInfo = VersionInfo( if (math.random < 0.5) __1 else __2, __2)
Run Code Online (Sandbox Code Playgroud)

?在您当前的代码中,编译器没有理由拒绝这一点。这意味着您VersionInfo打破了外部常量 与内部值 和 之间的常量路径__1__2这些路径major存储minor在您的VersionInfo. 例如,一旦传递__1as majorto ,与 相同类型的VersionInfo foo信息就会永远丢失。__1.Natfoo.major.Nat

只需不丢弃此类型信息,而是将其作为类型参数附加到VersionInfo.

假设你的自然数看起来有点像这样:

sealed trait NaturalNumber
class _3 extends NaturalNumber
class _2 extends _3
class _1 extends _2
class _0 extends _1

class VersionNumber {
  type Nat <: NaturalNumber
}

val __0 = new VersionNumber { type Nat = _0 }
val __1 = new VersionNumber { type Nat = _1 }
val __2 = new VersionNumber { type Nat = _2 }
val __3 = new VersionNumber { type Nat = _3 }

type IsEqual[A, B] = A =:= B
type IsLessOrEqual[A, B] = A <:< B
Run Code Online (Sandbox Code Playgroud)

您可以定义VersionInfoResourceManifest如下:

case class VersionInfo[Major, Minor](
  major: VersionNumber { type Nat = Major },
  minor: VersionNumber { type Nat = Minor }
)

trait ResourceManifest {
  def getResource: Int
  type Major <: NaturalNumber
  type Minor <: NaturalNumber
}
Run Code Online (Sandbox Code Playgroud)

然后将它们用作 的参数类型getResource

def getResource[A, B]
  (manifest: ResourceManifest)
  (versionInfo: VersionInfo[A, B])
  (implicit
    maj_check: manifest.Major IsEqual A,
    min_check: manifest.Minor IsLessOrEqual B
  )
: Unit = println("it compiles, ship it")
Run Code Online (Sandbox Code Playgroud)

一个小测试:

val manifest21 = new ResourceManifest {
  def getResource = 21
  type Major = _2
  type Minor = _1
}

val manifest22 = new ResourceManifest {
  def getResource = 22
  type Major = _2
  type Minor = _2
}

getResource(manifest21)(VersionInfo(__2, __1))
getResource(manifest21)(VersionInfo(__2, __2))
// getResource(manifest22)(VersionInfo(__2, __1)) // won't compile, good
getResource(manifest22)(VersionInfo(__2, __2))
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,我尝试使用与几个月前您的答案中相同的名称。