【问题标题】:Scala: Type-level programming parameterised by nested type valueScala:由嵌套类型值参数化的类型级编程
【发布时间】:2018-07-24 15:35:55
【问题描述】:

目前我有一个实现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。但是,尽管我以多种不同的方式进行操作,但我仍在努力使其正常工作。例如,我尝试直接对依赖于路径的类型进行类型检查,但失败了。我还尝试根据VersionInfo 内部的类型定义MAJMIN,但类型约束不再按预期方式工作。我知道我们可能面临aux-pattern 之类的类似问题,但我正在努力寻找类似的解决方案来解决我的问题。

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

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

【问题讨论】:

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


    【解决方案1】:

    假设__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

    【讨论】:

    • 优秀。很好的解释。我咬了一口太大而无法咀嚼的东西,因为我想一次做两件事。你已经很好地解决了它的客户端问题。我想知道清单方面是否也可以解决。正如我所说,理想情况下,我希望清单中的版本信息通过值而不是类型提供。我尝试使用循环类型定义(即定义基本特征:type Minor = versionInfo.minor.Nat; val versionInfo: VersionInfo[Major,Minor] 或使用VersionInfo[_._],但我没有走多远。有什么办法让它工作吗?
    • @Mathsnoob 不确定您在后续问题中要问什么 - 似乎您可以像对待 VersionInfo 一样对待 ResourceManifest。我添加了一个替代解决方案,它省略了所有泛型类型参数并对称地处理清单和信息。不会改变这个答案,因为它在我最初理解的意义上运行得很好。
    【解决方案2】:

    类似但略有不同的替代解决方案,不使用VersionInfoResourceManifest 上的任何类型参数,而是依赖Aux-typedefs。

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

    这是一堵代码的全部内容:

    sealed trait NaturalNumber
    class _3 extends NaturalNumber
    class _2 extends _3
    class _1 extends _2
    class _0 extends _1
    
    abstract class VersionNumber {
      type Nat <: NaturalNumber
      def toInt: Int
    }
    
    val __0 = new VersionNumber { type Nat = _0 ; def toInt = 0 }
    val __1 = new VersionNumber { type Nat = _1 ; def toInt = 1 }
    val __2 = new VersionNumber { type Nat = _2 ; def toInt = 2 }
    val __3 = new VersionNumber { type Nat = _3 ; def toInt = 3 }
    
    object VersionNumber {
      type Aux[N <: NaturalNumber] = VersionNumber {
        type Nat = N
      }
    }
    
    type IsEqual[A, B] = A =:= B
    type IsLessOrEqual[A, B] = A <:< B
    
    abstract class VersionInfo {
      type Major <: NaturalNumber
      type Minor <: NaturalNumber
      val major: VersionNumber.Aux[Major]
      val minor: VersionNumber.Aux[Minor]
    }
    
    object VersionInfo {
      type Aux[A <: NaturalNumber, B <: NaturalNumber] = VersionInfo {
        type Major = A
        type Minor = B
      }
      def apply[A <: NaturalNumber, B <: NaturalNumber](
        a: VersionNumber { type Nat = A },
        b: VersionNumber { type Nat = B }
      ): VersionInfo.Aux[A, B] = new VersionInfo {
        type Major = A
        type Minor = B
        val major = a
        val minor = b
      }
    }
    
    abstract class ResourceManifest {
    
      type Major <: NaturalNumber
      type Minor <: NaturalNumber
      val major: VersionNumber.Aux[Major] 
      val minor: VersionNumber.Aux[Minor]
    
      def getResource: Int = major.toInt * 10 + minor.toInt
    }
    
    object ResourceManifest {
      type Aux[A <: NaturalNumber, B <: NaturalNumber] = ResourceManifest {
        type Major = A
        type Minor = B
      }
      def apply[A <: NaturalNumber, B <: NaturalNumber](
        a: VersionNumber { type Nat = A },
        b: VersionNumber { type Nat = B }
      ): ResourceManifest.Aux[A, B] = new ResourceManifest {
        type Major = A
        type Minor = B
        val major = a
        val minor = b
      }
    }
    
    def getResource[
      MnfMaj <: NaturalNumber, 
      MnfMin <: NaturalNumber, 
      VrsMaj <: NaturalNumber, 
      VrsMin <: NaturalNumber
    ]
      (manifest: ResourceManifest.Aux[MnfMaj, MnfMin])
      (versionInfo: VersionInfo.Aux[VrsMaj, VrsMin])
      (implicit
        maj_check: MnfMaj IsEqual VrsMaj,
        min_check: MnfMin IsLessOrEqual VrsMin
      )
    : Unit = println("it compiles, ship it")
    
    
    
    val manifest21 = ResourceManifest(__2, __1)
    val manifest22 = ResourceManifest(__2, __2)
    
    getResource(manifest21)(VersionInfo(__2, __1))
    getResource(manifest21)(VersionInfo(__2, __2))
    // getResource(manifest22)(VersionInfo(__2, __1)) // still won't compile, good
    getResource(manifest22)(VersionInfo(__2, __2))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-01-13
      • 1970-01-01
      • 2013-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-10
      • 1970-01-01
      相关资源
      最近更新 更多