代码之家  ›  专栏  ›  技术社区  ›  Maths noob

scala:由嵌套类型值表示的类型级编程参数

  •  5
  • Maths noob  · 技术社区  · 6 年前

    目前我有一个程序可以实现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用户可能对此并不太满意。也, 获取资源 获取主版本和次版本的独立参数。

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

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

    所以我的舱单是这样的:

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

    同样,也有:

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

    并通过从包装器版本值类中提取版本类型来执行类型级别约束: VersionInfo . 然而,尽管我用了很多不同的方法,我仍在努力让它工作。例如,我尝试使用路径依赖类型直接执行类型检查,但失败了。我也试着定义 少校 根据里面的类型 版本信息 但是类型约束不再像预期的那样工作。我知道我们可能面临类似的问题 aux-pattern 但我正在努力找到一个与我的问题类似的解决方案。

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

    我为什么不能这样做,有什么理由吗?如果没有,我该怎么做?

    2 回复  |  直到 6 年前
        1
  •  2
  •   Andrey Tyukin    6 年前

    假设 __1 __2 是两个延伸的物体 VersionNumber ,有两种不同的类型 _1, _2 那延伸 Nat 在他们里面。编译器拒绝编译有什么原因吗

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

    ?在您当前的代码中,编译器没有理由拒绝它。这意味着你的 VersionInfo 打破外部常量之间的常量路径 γ1 , γ2 以及内在价值观 major minor ,存储在您的 版本信息 . 例如,一旦你通过 γ1 作为 专业 版本信息 foo ,信息 __1.Nat 与相同类型 foo.major.Nat 永远失去。

    这很容易解决,只需不丢弃此类型信息,而是将其作为类型参数附加到 版本信息 .

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

    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
    

    你可以定义 版本信息 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 .

        2
  •  1
  •   Andrey Tyukin    6 年前

    类似但略有不同的替代解决方案,不使用任何类型参数 VersionInfo ResourceManifest 而是依靠 Aux -Type

    允许同时实例化 版本信息 S和 资源清单 来自于 VersionNumber 对象,不使用任何显式类型参数。

    以下是整个代码墙:

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