Shapeless是否内置HList元素统一类型的编译期证明机制?
ForAll type class for verifying HList element types at compile time? Problem Statement
I implemented a custom ForAll type class to enforce that every element in an HList is of a specified type T at compile time, along with an example usage to chain functions on a value:
import shapeless._ @annotation.implicitNotFound("Cannot prove that A =:= ${T} forAll A in ${H}") trait ForAll[H <: HList, T] object ForAll { implicit def head[T]: ForAll[T :: HNil, T] = new ForAll[T :: HNil, T] {} implicit def tail[T, HT <: HList](implicit ttail: ForAll[HT, T]): ForAll[T :: HT, T] = new ForAll[T :: HT, T] {} } // Example usage def chain[H <: HList, A](hlist: H)(a: A)(implicit allAreFunctions: ForAll[H, (A => A)]): A = { def iter(h: HList, ax: A): A = h match { case HNil => ax case (f: (A => A)) :: tail => iter(tail, f(ax)) } iter(hlist, a) } val hlist1 = ((_: Int) + 1) :: ((_: Int) * 1) :: ((_: Int) - 2) :: HNil val hlist2 = ((_: Int).toString) :: ((_: Int) + 1) :: ((_: Int) - 2) :: HNil chain(hlist1)(1) // Compiles successfully // chain(hlist2)(1) // Fails to compile as expected
Since the ForAll instance proves type correctness, I can safely ignore the compiler's pattern matching warning. I'm wondering if Shapeless provides a built-in implementation for this, or if there's a better approach.
Answer
Great news—Shapeless does have a built-in type class exactly for this use case: shapeless.Every. It’s designed to verify that every element of an HList (or even a Coproduct) conforms to a given type, and it comes with extra utilities that make your code cleaner.
How to use Every instead of your custom ForAll
First, replace your ForAll with shapeless.Every, and adjust your chain function to leverage Every's built-in functionality. Unlike your custom type class, Every gives you a safe runtime way to convert the HList into a List[T], eliminating the need for pattern matching (and those warnings entirely):
import shapeless._ import shapeless.Every def chain[H <: HList, A](hlist: H)(a: A)(implicit ev: Every[H, A => A]): A = { ev.values.foldLeft(a)((acc, f) => f(acc)) } // Same test cases as before val hlist1 = ((_: Int) + 1) :: ((_: Int) * 1) :: ((_: Int) - 2) :: HNil val hlist2 = ((_: Int).toString) :: ((_: Int) + 1) :: ((_: Int) - 2) :: HNil chain(hlist1)(1) // Returns 0, works as expected // chain(hlist2)(1) // Fails to compile with a clear error: // "could not find implicit value for parameter ev: Every[shapeless.::[String => String, shapeless.::[Int => Int, shapeless.::[Int => Int, shapeless.HNil]]], Int => Int]"
Why Every is better than your custom ForAll
- No boilerplate: You don’t have to maintain your own type class instances—Shapeless handles the implicit derivation for HLists of any length.
- Runtime safety: The
valuesmethod gives you a typedList[T], so you can use standard Scala collection operations (likefoldLeft) instead of manual recursion and pattern matching. - Friendly error messages: The implicit not found errors are clear and descriptive, just like your custom annotation, but you don’t have to write the annotation yourself.
Under the hood
Every works almost exactly like your ForAll type class: it uses implicit recursion to verify each head element matches the target type, then recurses on the tail. The key difference is that it also captures the elements in a typed list, giving you runtime access without casting.
内容的提问来源于stack exchange,提问作者Vitalii Honta

