编译时检查 Nat 的总和?

Compile-time Check on Sum of Nat's?

我尝试编写类型类 SumEq5,使其 HList 类型参数的前两个字段加起来为 5:

trait SumEq5[A]
object SumEq5 {
  def apply[L <: HList](implicit ev: SumEq5[L]): SumEq5[L] = ev

  implicit def sumEq5Ev[L <: HList, A <: Nat, B <: Nat](
    implicit hcons: IsHCons.Aux[L, A, B :: HNil],
             ev: Sum.Aux[A, B, _5]
  ): SumEq5[L] = new SumEq5[L] {}
}

但它似乎不起作用:

import shapeless._
import shapeless.nat._
import net.SumEq5

scala> SumEq5[_0 :: _5 :: HNil]
<console>:19: error: could not find implicit value for 
    parameter ev: net.SumEq5[shapeless.::[shapeless.nat._0,shapeless.::
       [shapeless.nat._5,shapeless.HNil]]]
       SumEq5[_0 :: _5 :: HNil]

请提示为什么 _0 :: _5 :: HNil 没有证据表明它的两个 Nat 等于 5。

编辑

根据 Denis Rosca 在 shapeless's gitter 中的帮助更新了问题。

我只为您提供部分答案,即(解决方法)解决方案,但不理解为什么原始方案无法按预期工作。

看来你不能直接要求一个IsHCons.Aux[L, A, B :: HNil],你需要一点一点地做:

  1. IsHCons.Aux[L, A, L2],然后
  2. IsHCons.Aux[L2, B, HNil]

因此,编译:

import shapeless._, nat._, ops.hlist._, ops.nat._

trait SumEq5[A]
object SumEq5 {
  def apply[L <: HList](implicit ev: SumEq5[L]): SumEq5[L] = ev

  implicit def sumEq5Ev[L <: HList, L2 <: HList, A <: Nat, B <: Nat](
    implicit hcons0: IsHCons.Aux[L, A, L2],
             hcons: IsHCons.Aux[L2, B, HNil],
             ev: Sum.Aux[A, B, _5]
  ): SumEq5[L] = new SumEq5[L] {}
}

object T {
  def main(args: Array[String]): Unit = {
    SumEq5[_0 :: _5 :: HNil]
  }
}

根据 ,可以调整它以支持任何 2 个或更多元素的 HList,其中前两个元素的总和为 5,如下所示:

import shapeless._, nat._, ops.hlist._, ops.nat._

trait SumEq5[A]
object SumEq5 {
  def apply[L <: HList](implicit ev: SumEq5[L]): SumEq5[L] = ev

  implicit def sumEq5Ev[L1 <: HList, L2 <: HList, L3 <: HList, A <: Nat, B <: Nat](
    implicit hcons1: IsHCons.Aux[L1, A, L2],
             hcons2: IsHCons.Aux[L2, B, L3],
             ev: Sum.Aux[A, B, _5]
  ): SumEq5[L1] = new SumEq5[L1] {}
}

object T {
  def main(args: Array[String]): Unit = {
    SumEq5[_0 :: _5 :: HNil]
  }
}

Dale Wijnand 和 Marcus Henry 如果您想泛化到任意长度的 HLists,则指向正确的方向,但是如果您真的只想容纳两个元素 HLists,那么下面是一个比较简单的解决方案,

scala> import shapeless._, nat._, ops.nat._
import shapeless._
import nat._
import ops.nat._

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait SumEq5[A]

object SumEq5 {
  def apply[L <: HList](implicit ev: SumEq5[L]): SumEq5[L] = ev

  implicit def sumEq5AB[A <: Nat, B <: Nat]
    (implicit ev: Sum.Aux[A, B, _5]): SumEq5[A :: B :: HNil] =
      new SumEq5[A :: B :: HNil] {}
}

// Exiting paste mode, now interpreting.

defined trait SumEq5
defined object SumEq5

scala> SumEq5[_0 :: _5 :: HNil]
res0: SumEq5[_0 :: _5 :: HNil]] = SumEq5$$anon@658c5e59

这里的主要区别是实例是为两个元素列表明确定义的,而不是为一般列表定义的,前提是存在列表恰好有两个元素的证据。

根据 Dale 的更新,我们可以将其概括为包含至少两个(而不是恰好两个)元素的 HLists,同样没有任何额外的见证,

scala> import shapeless._, nat._, ops.nat._
import shapeless._
import nat._
import ops.nat._

scala> :paste
// Entering paste mode (ctrl-D to finish)

trait SumEq5[A]

object SumEq5 {
  def apply[L <: HList](implicit ev: SumEq5[L]): SumEq5[L] = ev

  implicit def sumEq5AB[A <: Nat, B <: Nat, T <: HList]
    (implicit ev: Sum.Aux[A, B, _5]): SumEq5[A :: B :: T] =
      new SumEq5[A :: B :: T] {}
}

// Exiting paste mode, now interpreting.

defined trait SumEq5
defined object SumEq5

scala> SumEq5[_0 :: _5 :: HNil]
res0: SumEq5[_0 :: _5 :: HNil]] = SumEq5$$anon@658c5e59