You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Shapeless是否内置HList元素统一类型的编译期证明机制?

Does Shapeless have a built-in alternative to a custom 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 values method gives you a typed List[T], so you can use standard Scala collection operations (like foldLeft) 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:31:02