简体   繁体   中英

Compile-time Check on Sum of Nat's?

I attempted to write a typeclass, SumEq5 , such that its HList type parameter's first two fields add up to 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] {}
}

But it doesn't appear to work:

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]

Please give me a hint as to why _0 :: _5 :: HNil does not have evidence that its two Nat 's are equal to 5.

EDIT

Updated question per Denis Rosca's help in shapeless's gitter .

I only have a partial answer for you, ie a (workaround) solution without understanding why exactly the original doesn't work as intended.

Seems you can't ask directly for a IsHCons.Aux[L, A, B :: HNil] , you need to do it piecemeal:

  1. IsHCons.Aux[L, A, L2] , and then
  2. IsHCons.Aux[L2, B, HNil]

Therefore, this compiles:

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]
  }
}

Following from Miles Sabin's answer , this can be tweaked to support any HList of 2 or more elements, of which the sum of the first two is 5, like so:

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 and Marcus Henry are pointing in the right direction if you want to generalize to HList s of arbitrary length, however if you really only want to accommodate two element HList s, then the following is a rather simpler solution,

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$1@658c5e59

The main difference here is that the instance is explicitly defined for two element lists rather than being defined for lists in general with the proviso that there exists a proof that the list has exactly two elements.

Following Dale's update, we can generalize this to accommodate HList s with at least two (rather than exactly two) elements, again without any additional witnesses,

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$1@658c5e59

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM