Reputation: 1240
Given the following enum:
enum Connector:
case CHAdeMO
case Mennekes
case CCS
case Tesla
Is there a type like Map[ConnectorType, Int]
but which would produce a compile error for:
Map(
Connector.CHAdeMO -> 1,
Connector.Mennekes -> 2,
Connector.CCS -> 3,
)
That is the map does not contain a key for Connector.Tesla
. In other words, in compile time the type should be akin to ((Connector.CHAdeMO, Int), (Connector.Mennekes, Int), (Connector.CCS, Int), (Connector.Tesla, Int))
but behave like a regular Map otherwise.
Upvotes: 6
Views: 167
Reputation: 7604
Here's a solution using tuples (Scastie):
opaque type CheckedMap <: Map[Connector, Int] = Map[Connector, Int]
type Contains[E, T <: Tuple] <: Boolean = T match {
case EmptyTuple => false
case h *: t =>
h match {
case (E, _) => true
case _ => Contains[E, t]
}
}
type ContainsAll[S <: Tuple, T <: Tuple] = S match {
case EmptyTuple => DummyImplicit
case h *: t =>
Contains[h, T] match {
case true => ContainsAll[t, T]
case false => Nothing
}
}
type AllConnectors = (
Connector.CHAdeMO.type,
Connector.Mennekes.type,
Connector.CCS.type,
Connector.Tesla.type
)
def checkedMap[T <: Tuple](t: T)(using
@annotation.implicitNotFound(
"Not all Connector types given."
) c: ContainsAll[AllConnectors, T]
): CheckedMap = t.toList.asInstanceOf[List[(Connector, Int)]].toMap
This will take a tuple of tuples (Connector, Int)
and check if it contains all the types in another tuple of all Connector
types. If the input contains all the connector types at least once, it looks for an implicit DummyImplicit
, otherwise, it looks for an implicit Nothing
, which it obviously doesn't find. The resulting error message is quite lengthy and unhelpful, so I put in a custom error message. Note that this doesn't check if there are duplicate keys, but could be trivially modified to do so.
Unfortunately, I found myself having to explicitly annotate the key-value pairs' types at the use site:
//Errors because Tesla is missing
checkedMap(
(
Connector.CHAdeMO -> 1: (Connector.CHAdeMO.type, Int),
Connector.Mennekes -> 2: (Connector.Mennekes.type, Int),
Connector.CCS -> 3: (Connector.CCS.type, Int)
)
)
//Valid
checkedMap(
(
Connector.CHAdeMO -> 1: (Connector.CHAdeMO.type, Int),
Connector.Mennekes -> 2: (Connector.Mennekes.type, Int),
Connector.CCS -> 3: (Connector.CCS.type, Int),
Connector.Tesla -> 4: (Connector.Tesla.type, Int)
)
)
Upvotes: 5