这是可以做到的,并且有一些有趣的细节。
第一个是,通常情况下,要匹配使用右结合构造函数(即
::
)构建的结构,您将使用相应的右结合提取器,否则您将以相反的顺序分解并绑定提取的元素。不幸的是,与右结合运算符一样,右结合提取器必须以Scala中的
:
结尾,否则提取器名称必须为
~:
而不是普通的
~
,这将破坏您的解析组合语法。但是,我将暂时搁置这个问题,并继续使用右结合性。
第二个细节是,我们需要使unapply方法根据我们是否匹配了两个或两个以上元素的
HList
来产生不同类型的结果(我们根本不能匹配少于两个元素的列表)。
如果我们匹配的是两个以上元素的列表,我们需要将列表分解为由头和
HList
尾部组成的一对,即给定
l:H :: T
,其中
T <: HList
,我们必须生成类型为
(H,T)
的值。另一方面,如果我们匹配的是正好两个元素的列表,即
E1 :: E2 :: HNil
的形式,我们需要将列表分解为仅包含这两个元素的一对,即
(E1,E2)
,而不是一个头和一个尾部,后者将是
(E1,E2 :: HNil)
。
这可以使用与shapeless中使用的完全相同的类型级编程技术来完成。首先,我们定义一个类型类,该类型类将执行提取器的工作,并且每个实例都对应于上述两种情况之一。
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类型类必须从列表末尾而不是开头剥离元素。我们可以借助shapeless的
Init
和
Last
类型类来实现这一点。
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