无形:复杂的 HList 约束

Shapeless: complex HList Constraint

我有以下方法:

import shapeless._
import shapeless.UnaryTCConstraint._
def method[L <: HList : *->*[Seq]#λ](list: L) = println("checks")

它使我能够确保发生以下情况:

val multipleTypes = "abc" :: 1 :: 5.5 :: HNil
val onlyLists = Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil

method(multipleTypes)  // throws could not find implicit value ...
method(onlyList) // prints checks

如何使用另一个参数列表扩充 method,例如:

def method2[L <: HList : *->*[Seq]#λ, M <: HList](list: L)(builder: M => String) = println("checks")

但是限制了HList M必须和HList L的大小相同,并且只能包含HList L内部类型的元素。举个例子:

// This should work
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
  case a :: 1 :: d :: HNil => "works"
}
// This should throw some error at compile time, because the second element is Seq[Int]
// therefore in the builder function I would like the second element to be of type Int.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
  case a :: true :: d :: HNil => "fails"
}
// This should also fail because the HLists are not of the same length.
method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil){
  case 1 :: d :: HNil => "fails"
}

如果我这样定义method2

def method2[L <: HList : *->*[Seq]#λ](list: L)(builder: L => String) = println("checks")

几乎解决了问题,唯一缺少的是构建器将具有 Seq[T] 类型的元素而不是 T 类型的元素。

这是 ops.hlist.Comapped 的案例。

您希望将 method2 定义为

def method2[L <: HList : *->*[Seq]#λ, M <: HList]
  (list: L)
  (builder: M => String)
  (implicit ev: Comapped.Aux[L, Seq, M])

但这行不通,因为应该在对 builder 参数进行类型检查之前计算类型 M

所以实际的实现是这样的:

class Impl[L <: HList : *->*[Seq]#λ, M <: HList]
  (list: L)
  (implicit ev: Comapped.Aux[L, Seq, M]) 
{
  def apply(builder: M => String) = println("checks")
}

def method2[L <: HList : *->*[Seq]#λ, M <: HList]
  (list: L)
  (implicit ev: Comapped.Aux[L, Seq, M]) = new Impl[L, M](list)

而且你不能直接调用它。您可以使用额外的 apply 或其他方法来隐式提供隐式参数列表:

method2(Seq("abc") :: Seq(1) :: Seq(5.5) :: HNil) apply {
  case a :: 1 :: d :: HNil => "works"
}