Scala:由嵌套类型值

时间:2018-07-24 15:35:56

标签: scala type-constraints type-level-computation

当前,我有一个实现peano算术的程序:

sealed trait NaturalNumber

和一个名为getResource的函数,该函数在具有接口的参数中获取自然数类型值:

sealed trait VersionNumber {
  type Nat <: NaturalNumber
}

并根据此接口中提供的参考类型值版本号MAJMIN检查这些值:

trait ResourceManifest {
  def getResource: Int
  type Major <: NaturalNumber
  type Minor <: NaturalNumber
}

根据哪个函数编译或不编译。该函数具有以下形式:

    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

这里是full code。 (如果您喜欢类型递归,则可以选择implementation。)

实际上,这是通过覆盖普通Scala用户可能不太喜欢的类型值来实现的。另外,getResource为主要版本和次要版本分别获取参数。

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

case class VersionInfo(major: VersionNumber, minor: VersionNumber)

这样我的清单是这样的:

trait ResourceManifestRefactored {
  def getResource: Int
  val versionInfo: VersionInfo
}

并且类似地具有:

def getResourceRefactored(manifest: ResourceManifestRefactored)(versionInfo: VersionInfo)

并通过从包装程序版本值类VersionInfo中选择版本类型来进行类型级别约束。但是,尽管我以许多不同的方式来做,但我仍在努力使其工作。例如,我尝试直接使用与路径相关的类型进行类型检查,但失败了。我还尝试根据MAJ内部的类型定义MINVersionInfo,但是类型约束不再按预期的方式工作。我了解我们可能正面临aux-pattern之类解决的类似问题,但是我正在努力为我的问题提供类似的解决方案。

本质上,我想让预定义的对象包装类型,并且我想通过这些对象而不是直接对类型进行类型约束。

我有不能做的根本原因吗?如果没有,我该怎么做?

2 个答案:

答案 0 :(得分:2)

假设__1__2是两个扩展VersionNumber的对象,两个不同类型的_1, _2在其中扩展了Nat。编译器会拒绝编译的任何原因

val foo: VersionInfo = VersionInfo( if (math.random < 0.5) __1 else __2, __2)

?在您当前的代码中,编译器没有理由拒绝它。这意味着您的VersionInfo破坏了存储在您内部的外部常量__1__2与内部值majorminor之间的常量路径VersionInfo。例如,将__1作为major传递到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

您可以如下定义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
}

,然后将它们用作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")

一点测试:

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))

在上面的代码中,我尝试使用相同的名称as in this answer of yours from few months ago

答案 1 :(得分:1)

类似但略有不同的替代解决方案,它不使用:makeENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom"," jar","app.jar"]上的任何类型参数,而是依靠VersionInfo-typedef。

允许从ResourceManifest对象实例化AuxVersionInfo,而无需在任何地方使用任何显式类型参数。

这里就是一堵代码墙:

ResourceManifest