这是可以做到的,并且有一些有趣的转折。
第一个是,通常,为了匹配使用右关联构造函数(即::
)构建的结构,您将使用相应的右关联提取器,否则您将以相反的顺序分解和绑定提取的元素。不幸的是,右关联提取器必须像右关联运算符一样,在 Scala 中以 a 结尾,:
这会破坏您的解析器组合器语法,因为提取器名称必须~:
代替 plain ~
。但是,我将暂时推迟此操作并使用正确的关联性。
第二个转折点是,我们需要 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 类型类的提取器工作,必须从列表的末尾而不是从列表的开头剥离元素。我们可以借助 shapelessInit
和Last
type 类来做到这一点,
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