我可以使用与无形副产品匹配的模式吗?
import shapeless.{CNil, :+:}
type ListOrString = List[Int] :+: String :+: CNil
def f(a: ListOrString): Int = a match {
case 0 :: second :: Nil => second
case first :: Nil => first
case Nil => -1
case string: String => string.toInt
}
这当然不起作用,因为a
被装箱为Coproduct
。
是否有其他方法可以使用副产品并保持模式匹配的能力?
答案 0 :(得分:17)
您可以在模式匹配中使用Inl
和Inr
构造函数:
import shapeless.{ CNil, Inl, Inr, :+: }
type ListOrString = List[Int] :+: String :+: CNil
def f(a: ListOrString): Int = a match {
case Inl(0 :: second :: Nil) => second
case Inl(first :: Nil) => first
case Inl(Nil) => -1
case Inr(Inl(string)) => string.toInt
}
这种方法并不理想,因为如果你希望编译器能够告诉匹配是详尽的,你必须处理CNil
情况 - 我们知道这种情况不可能匹配,但是编译器没有,所以我们必须做这样的事情:
def f(a: ListOrString): Int = a match {
case Inl(0 :: second :: Nil) => second
case Inl(first :: Nil) => first
case Inl(Nil) => -1
case Inl(other) => other.sum
case Inr(Inl(string)) => string.toInt
case Inr(Inr(_)) => sys.error("Impossible")
}
我个人也发现导航到副产品中的相应位置Inr
和Inl
有点违反直觉。
通常,最好使用多态函数值折叠副产品:
object losToInt extends shapeless.Poly1 {
implicit val atList: Case.Aux[List[Int], Int] = at {
case 0 :: second :: Nil => second
case first :: Nil => first
case Nil => -1
case other => other.sum
}
implicit val atString: Case.Aux[String, Int] = at(_.toInt)
}
def f(a: ListOrString): Int = a.fold(losToInt)
现在,编译器将验证穷举,而无需处理不可能的情况。
答案 1 :(得分:6)
我刚刚提交了Shapeless拉取请求here,可能会很好地满足您的需求。 (请注意,它只是一个拉取请求,可能会进行修改或被拒绝......但如果您觉得它很有用,请随意使用机器并在您自己的代码中使用它。)
来自提交消息:
[...] Int:类型的Coproduct c:+:String:+:Boolean:+:CNil可以 被折叠成双人如下:
val result = c.foldCases[Double]
.atCase(i => math.sqrt(i))
.atCase(s => s.length.toDouble)
.atCase(b => if (b) 100.0 else -1.0)
这提供了优于现有折叠方法的一些益处 余积。与Folder类类不同,这个类不需要 具有稳定标识符的多态函数,所以语法是 有点轻巧,更适合折叠的情况 函数不被重用(例如,解析器组合库)。
此外,与使用模式直接折叠Coproduct不同 在Inl和Inr注入器上匹配,这种类型保证了这一点 得到的折叠是详尽的。部分也可以 折叠副产品(只要按指定的顺序处理案例 通过Coproduct类型签名),这使得它成为可能 逐步折叠Coproduct。
对于您的示例,您可以这样做:
def f(a: ListOrString): Int = a.foldCases[Int]
.atCase(list => list match {
case 0 :: second :: Nil => second
case first :: Nil => first
case Nil => -1
case other => other.sum
})
.atCase(s => s.toInt)