模式与无形副产品匹配

时间:2015-12-05 16:27:55

标签: scala pattern-matching shapeless

我可以使用与无形副产品匹配的模式吗?

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

是否有其他方法可以使用副产品并保持模式匹配的能力?

2 个答案:

答案 0 :(得分:17)

您可以在模式匹配中使用InlInr构造函数:

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

我个人也发现导航到副产品中的相应位置InrInl有点违反直觉。

通常,最好使用多态函数值折叠副产品:

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)