Alban Dericbourg
Alban Dericbourg

Reputation: 1636

Constraint on HList: check for single occurrence of a type

I'm trying to add a constraint on an HList (from Shapeless):

My example has this type hierarchy:

trait T
case class TA extends T
case class TB extends T

To give examples:

I cannot figure out how to express this as a constraint.

Upvotes: 9

Views: 380

Answers (1)

Travis Brown
Travis Brown

Reputation: 139038

You can do this with a custom type class that witnesses that there's exactly one TB and all the other elements are TA. If you imagine building up this list inductively, you'll see there are two cases you need to handle—either everything you've seen so far is a TA (which we can witness with a ToList[T, TA]) and the current element is a TB, or you've already seen a single TB and the current element is a TA:

import shapeless._, ops.hlist.{ ToList }

trait T
case class TA() extends T
case class TB() extends T

trait UniqueTB[L <: HList] extends DepFn1[L] {
  type Out = TB
  def apply(l: L): TB
}

object UniqueTB {
  def apply[L <: HList](implicit utb: UniqueTB[L]): UniqueTB[L] = utb
  def getTB[L <: HList](l: L)(implicit utb: UniqueTB[L]): TB = utb(l)

  implicit def firstTB[T <: HList](
    implicit tl: ToList[T, TA]
  ): UniqueTB[TB :: T] = new UniqueTB[TB :: T] {
    def apply(l: TB :: T): TB = l.head
  }

  implicit def afterTB[T <: HList](
    implicit utb: UniqueTB[T]
  ): UniqueTB[TA :: T] = new UniqueTB[TA :: T] {
    def apply(l: TA :: T): TB = utb(l.tail)
  }
}

And then:

scala> UniqueTB[TB :: HNil]
res0: UniqueTB[shapeless.::[TB,shapeless.HNil]] = UniqueTB$$anon$1@385c6929

scala> UniqueTB[TA :: TB :: HNil]
res1: UniqueTB[shapeless.::[TA,shapeless.::[TB,shapeless.HNil]]] = UniqueTB$$anon$2@682dd97e

scala> UniqueTB[TA :: TB :: TA :: HNil]
res2: UniqueTB[shapeless.::[TA,shapeless.::[TB,shapeless.::[TA,shapeless.HNil]]]] = UniqueTB$$anon$2@5ef48f82

scala> UniqueTB[TB :: HNil]
res3: UniqueTB[shapeless.::[TB,shapeless.HNil]] = UniqueTB$$anon$1@33be241

scala> UniqueTB[TA :: HNil]
<console>:25: error: could not find implicit value for parameter utb: UniqueTB[shapeless.::[TA,shapeless.HNil]]
       UniqueTB[TA :: HNil]
               ^

scala> UniqueTB[HNil]
<console>:25: error: could not find implicit value for parameter utb: UniqueTB[shapeless.HNil]
       UniqueTB[HNil]
               ^

scala> UniqueTB[TB :: TB :: HNil]
<console>:25: error: could not find implicit value for parameter utb: UniqueTB[shapeless.::[TB,shapeless.::[TB,shapeless.HNil]]]
       UniqueTB[TB :: TB :: HNil]
               ^

I've given the type class an operation that returns the TB, but if you don't need that you could leave it method-less.

Upvotes: 10

Related Questions