scala – 绕过路径依赖类型无法保留依赖值
考虑以下:
trait Platform { type Arch <: Architecture def parseArch(str: String): Option[Arch] } object Platform { def parse(str: String): Option[Platform] = ??? } trait Architecture def main() { def exec(p: Platform)(a: p.Arch) = ??? Platform.parse("ios") .flatMap(p => p.parseArch("arm64").map(a => (p,a))) .flatMap { case (p,a) => exec(p)(a) } // <----- This fails to compile } exec(p)(a)无法编译并显示错误消息:
从错误消息来看,似乎scalac无法保留Arch所依赖的值(p),因此它选择键入投影(尽管我不太确定A $A2.this是什么意思). 为了它的价值,用以下代码替换最后一行将编译: .flatMap(p => exec(p)(p.parseArch("arm64").get)) 这是scala编译器的限制还是我在这里遗漏了一些东西? 解决方法
简单的解决方案
处理路径依赖类型时最好的选择是始终保持所有者的价值,因为Scala的推理和推理能力非常有限. 例如,您的代码示例可以重写为: Platform.parse("ios") flatMap { p => p.parseArch("arm64").map(exec(p)) } 通常可以执行这样的重写,尽管代码通常会变得不那么简洁和优雅.通常的做法是使用依赖函数和参数类. 使用依赖类型 在您的示例中,代码: Platform.parse("ios").flatMap(p => p.parseArch("arm64").map(a => (p,a))) 类型为Option [(Platform,Platform#Arch)],因为Scala的推断不能保留元组的第二个元素依赖于第一个元素的事实. (你得到A $A2.this.Platform,因为你在某些内部环境中声明了Platform.) 换句话说,Scala的Tuple2类型不依赖.我们可以通过创建自己的类来纠正这个问题: case class DepPair(p: Platform)(a: p.Arch) 但是,Scala还不支持依赖类签名,也不会编译.相反,我们设置使用特征: trait Dep { val plat: Platform val arch: plat.Arch } Platform.parse("ios") .flatMap { p => p.parseArch("arm64").map { a => new Dep { val plat: p.type = p; val arch: p.Arch = a }}} .flatMap { dep => exec(dep.plat)(dep.arch) } 请注意val plat和val arch上的ascription,因为没有它们,Scala将尝试推断一个将使类型检查失败的精炼类型. 事实上,我们处于Scala(恕我直言)的合理范围内.例如,如果我们有参数化特征Dep [P<:Platform],我们就会遇到各种各样的问题.值得注意的是: Error:(98,15) type mismatch; found : Platform => Option[Dep[p.type]] forSome { val p: Platform } required: Platform => Option[B] Scala推断出一种存在函数类型,但我们想要的实际上是在函数类型中进行存在量化.我们必须引导Scala了解这一点,我们最终得到的结果如下: Platform.parse("ios").flatMap[Dep[p.type] forSome { val p: Platform }]{ case p => p.parseArch("arm64").map{case a: p.Arch => new Dep[p.type] { val plat: p.type = p; val arch = a }}} .flatMap { dep => exec(dep.plat)(dep.arch) } 现在我会让你决定哪种方式是最好的:坚持主人val(简单的解决方案),否则可能会失去你留下的任何理智感! 但谈到失去理智和存在感,让我们再尝试进一步研究…… 使用存在(失败) 代码中间结果的问题类型是Option [(Platform,Platform#Arch)]. Option[(p.type,p.Arch) forSome {val p: Platform}] 我们可以通过显式指定它来帮助Scala,因此中间结果具有预期的类型: val tmp: Option[(p.type,p.Arch) forSome {val p: Platform}] = Platform.parse("ios") .flatMap { case p => p.parseArch("arm64").map { a => (p,a): (p.type,p.Arch) }} 但是,我们现在触摸Scala类型系统的一个非常敏感的区域,它经常会导致问题.事实上,我没有找到表达第二个flatMap的方法…… 尝试tmp.flatMap {case(p,a)=> exec(p)(a)}给出了非常有帮助的: Error:(30,30) type mismatch; found : a.type (with underlying type p.Arch) required: p.Arch 另一项试验: tmp.flatMap { (tup: (p.type,p.Arch) forSome {val p: Platform}) => exec(tup._1)(tup._2) } Error:(32,79) type mismatch; found : tup._2.type (with underlying type p.Arch) required: tup._1.Arch 在这一点上,我认为任何合理的人都会放弃 – 并且可能会远离Scala编程几天;-) (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |