当前,我有一个实现peano算术的程序:
sealed trait NaturalNumber
和一个名为getResource
的函数,该函数在具有接口的参数中获取自然数类型值:
sealed trait VersionNumber {
type Nat <: NaturalNumber
}
并根据此接口中提供的参考类型值版本号MAJ
和MIN
检查这些值:
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
内部的类型定义MIN
和VersionInfo
,但是类型约束不再按预期的方式工作。我了解我们可能正面临aux-pattern之类解决的类似问题,但是我正在努力为我的问题提供类似的解决方案。
本质上,我想让预定义的对象包装类型,并且我想通过这些对象而不是直接对类型进行类型约束。
我有不能做的根本原因吗?如果没有,我该怎么做?
答案 0 :(得分:2)
假设__1
和__2
是两个扩展VersionNumber
的对象,两个不同类型的_1, _2
在其中扩展了Nat
。编译器会拒绝编译的任何原因
val foo: VersionInfo = VersionInfo( if (math.random < 0.5) __1 else __2, __2)
?在您当前的代码中,编译器没有理由拒绝它。这意味着您的VersionInfo
破坏了存储在您内部的外部常量__1
,__2
与内部值major
和minor
之间的常量路径VersionInfo
。例如,将__1
作为major
传递到VersionInfo
foo
时,__1.Nat
与foo.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
您可以如下定义VersionInfo
和ResourceManifest
:
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)
类似但略有不同的替代解决方案,它不使用:make
或ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom"," jar","app.jar"]
上的任何类型参数,而是依靠VersionInfo
-typedef。
允许从ResourceManifest
对象实例化Aux
和VersionInfo
,而无需在任何地方使用任何显式类型参数。
这里就是一堵代码墙:
ResourceManifest