Alban Dericbourg - 3 months ago 11
Scala Question

# Constraint on HList: check for single occurrence of a type

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

• it should contain any arbitrary number of elements of type
`TA`
(from 0 to N);

• it should contain one and only one element of type
`TB`
.

My example has this type hierarchy:

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

To give examples:

• `tb :: HNil`
is valid

• `ta :: tb ::HNil`
is valid

• `ta :: tb :: ta :: HNil`
is valid

• `ta :: HNil`
is invalid

• `HNil`
is invalid

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

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.