使用Shapeless HList与不变容器的映射/过滤问题求助
解决不变Box类型下HList的映射与过滤问题
问题根源
你的代码报错和过滤失效的核心原因是**Box[E]在E上的不变性**:虽然Water是Drink[Int]的子类,但由于不变性,Box[Water]并不是Box[Drink[Int]]的子类型。这就导致:
pour的隐式case无法匹配Box[Water](因为它不是Box[Drink[Int]]的子类);covariantFilter[Box[Drink[Int]]]找不到任何匹配元素,返回HNil。
解决方案一:修复Poly1映射(pour)
我们需要调整pour的隐式定义逻辑,直接针对Box内部的E进行类型约束,而不是约束Box本身是Box[Drink[X]]的子类。修改后的pour如下:
object pour extends Poly1 { // 匹配所有Box[E],其中E是Drink[Int]的子类 implicit def caseDrinkInt[E <: Drink[Int]] = at[Box[E]] { o => Box(o.elem.v * 2) } // 匹配所有Box[E],其中E是Drink[BigDecimal]的子类 implicit def caseDrinkDec[E <: Drink[BigDecimal]] = at[Box[E]] { o => Box(o.elem.v + 5.0) } }
这个修改的关键在于:我们不再要求输入类型是Box[Drink[Int]]的子类,而是要求Box包裹的E是Drink[Int]的子类。由于Water <: Drink[Int],Box[Water]会匹配到第一个隐式case;同理Box[Squash]和Box[Juice]会匹配到第二个case。此时Proc.make()就能正常找到Mapper的隐式实例。
解决方案二:实现自定义协变过滤
原covariantFilter失效是因为它依赖于子类型关系,但Box的不变性破坏了这个关系。我们可以通过自定义判断逻辑的Poly1配合filterWith来实现需求:
// 定义一个Poly1,判断元素是否是包裹Drink[Int]子类的Box object isDrinkIntBox extends Poly1 { implicit def matches[E <: Drink[Int]] = at[Box[E]](_ => true) implicit def default[AnyOther] = at[AnyOther](_ => false) } // 使用示例: val filtered = Proc.drinks.filterWith(isDrinkIntBox) // filtered 类型为 Box[Water] :: HNil,值为 Box(Water(10)) :: HNil
如果需要更通用的过滤(比如过滤任意Drink子类的Box),可以把过滤逻辑抽象成类型类:
import shapeless._ trait IsBoxOf[B, D] object IsBoxOf { implicit def boxOfSubtype[E <: D, D] = new IsBoxOf[Box[E], D] {} } object filterBoxOf { def apply[D] = new Poly1 { implicit def caseBox[E <: D](implicit ev: IsBoxOf[Box[E], D]) = at[Box[E]](_ => true) implicit def default[AnyOther] = at[AnyOther](_ => false) } } // 使用时: val intDrinks = Proc.drinks.filterWith(filterBoxOf[Drink[Int]]) val decDrinks = Proc.drinks.filterWith(filterBoxOf[Drink[BigDecimal]])
关键思路总结
所有方案的核心都是绕过Box的不变性限制:
- 对于映射:直接约束
Box内部元素的类型,而非Box本身的子类型; - 对于过滤:通过自定义判断逻辑,检查
Box内部元素是否符合父类型要求,而非依赖Box的子类型关系。
这样既不需要修改Box的不变性,也不需要为每个Drink子类单独编写case,保证了代码的可扩展性。
内容的提问来源于stack exchange,提问作者schrödingcöder
相关产品推荐
相关产品推荐

