一个模拟解析器连接```的无形HList的提取器

时间:2013-08-27 14:42:54

标签: scala parser-combinators shapeless hlist

问题

以某种方式可以创建一个无形的提取器' HList如下所示。

val a ~ _ ~ b = 4 :: "so" :: 4.5 :: HNil
=> a == 4 && b == 4.5
  1. ::替换为~,这不应该是问题。
  2. 摆脱终止HNil。是否有可能出现的问题?
  3. 动机

    经过多少汗水和泪水,我设法到达了以下代码的作用点:

    for(
      x1 :: _ :: x2 :: HNil <- (expInt ~ "+" ~ expInt).llE
    ) yield (x1 + x2)
    

    expInt解析某个monad Int中的E(expInt ~ "+" ~ expInt).llE的类型为E[Int :: String :: Int :: HNil]

    我希望<-左侧的模式在某种程度上类似于右侧组合子解析器的构造。

1 个答案:

答案 0 :(得分:36)

这可以做到,并且有一些有趣的曲折。

第一个是,通常,为了匹配使用右关联构造函数构建的结构(即。::),您将使用相应的右关联提取器,否则您将反向分解和绑定提取的元素订购。不幸的是,正确的关联提取器必须像右关联运算符一样以Scala中的:结尾,这会破坏您的解析器组合器语法,因为提取器名称必须是~:而不是普通~ 。但是,我暂时推迟这一点并使用正确的关联性。

第二个问题是,我们需要使用unapply方法来生成不同类型的结果,具体取决于我们是否匹配多于两个元素的HList 两个元素(我们不应该匹配少于两个元素的列表)。

如果我们匹配两个以上元素的列表,我们需要将列表分解为由头部和HList尾部组成的对,即。给定l: H :: T其中T <: HList我们必须产生(H, T)类型的值。另一方面,如果我们匹配恰好两个元素的列表,即。在E1 :: E2 :: HNil形式中,我们需要将列表分解为仅包含这两个元素的对,即(E1, E2)而不是头部和尾部(E1, E2 :: HNil)

这可以使用与整个无形中使用的完全相同的类型级编程技术来完成。首先,我们定义一个类型类,它将完成提取器的工作,实例对应于上述两种情况中的每一种,

import shapeless._

trait UnapplyRight[L <: HList] extends DepFn1[L]

trait LPUnapplyRight {
  type Aux[L <: HList, Out0] = UnapplyRight[L] { type Out = Out0 }
  implicit def unapplyHCons[H, T <: HList]: Aux[H :: T, Option[(H, T)]] =
    new UnapplyRight[H :: T] {
      type Out = Option[(H, T)]
      def apply(l: H :: T): Out = Option((l.head, l.tail))
    }
}

object UnapplyRight extends LPUnapplyRight {
  implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] =
    new UnapplyRight[H1 :: H2 :: HNil] {
      type Out = Option[(H1, H2)]
      def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head))
    }
}

然后我们就这样定义我们的提取器,

object ~: {
  def unapply[L <: HList, Out](l: L)
    (implicit ua: UnapplyRight.Aux[L, Out]): Out = ua(l)
}

然后我们很高兴,

val l = 23 :: "foo" :: true :: HNil

val a ~: b ~: c = l
a : Int
b : String
c : Boolean

到目前为止,这么好。现在让我们回到关联性问题。如果我们想要使用左关联提取器(即。~而不是~:)获得相同的效果,我们需要更改分解的方式。首先让我们看看我们刚才使用的正确的关联提取器语法。表达式,

val a ~: b ~: c = l

相当于,

val ~:(a, ~:(b, c)) = l

相比之下,左联想版本

val a ~ b ~ c = l

相当于,

val ~(~(a, b), c) = l

为了使这个工作作为HLists的提取器,我们的unapply类类必须从结尾剥离元素,而不是从列表的开头。我们可以借助无形的InitLast类型来实现这一点,

trait UnapplyLeft[L <: HList] extends DepFn1[L]

trait LPUnapplyLeft {
  import ops.hlist.{ Init, Last }
  type Aux[L <: HList, Out0] = UnapplyLeft[L] { type Out = Out0 }
  implicit def unapplyHCons[L <: HList, I <: HList, F]
    (implicit
      init: Init.Aux[L, I],
      last: Last.Aux[L, F]): Aux[L, Option[(I, F)]] =
    new UnapplyLeft[L] {
      type Out = Option[(I, F)]
      def apply(l: L): Out = Option((l.init, l.last))
    }
}

object UnapplyLeft extends LPUnapplyLeft {
  implicit def unapplyPair[H1, H2]: Aux[H1 :: H2 :: HNil, Option[(H1, H2)]] =
    new UnapplyLeft[H1 :: H2 :: HNil] {
      type Out = Option[(H1, H2)]
      def apply(l: H1 :: H2 :: HNil): Out = Option((l.head, l.tail.head))
    }
}

object ~ {
  def unapply[L <: HList, Out](l: L)
    (implicit ua: UnapplyLeft.Aux[L, Out]): Out = ua(l)
}

现在我们已经完成了,

val a ~ b ~ c = l
a : Int
b : String
c : Boolean