编译时保证地图对每个枚举案例都有一个键
Compile time guarantee a map has a key for each enum case
给定以下枚举:
enum Connector:
case CHAdeMO
case Mennekes
case CCS
case Tesla
是否有类似 Map[ConnectorType, Int]
但会产生编译错误的类型:
Map(
Connector.CHAdeMO -> 1,
Connector.Mennekes -> 2,
Connector.CCS -> 3,
)
即地图不包含 Connector.Tesla
的键。换句话说,在编译时类型应该类似于 ((Connector.CHAdeMO, Int), (Connector.Mennekes, Int), (Connector.CCS, Int), (Connector.Tesla, Int))
但在其他方面表现得像一个普通的 Map 。
这是一个使用元组的解决方案 (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
这将采用元组 (Connector, Int)
的元组,并检查它是否包含所有 Connector
类型的另一个元组中的所有类型。如果输入至少包含一次所有连接器类型,它会查找隐式 DummyImplicit
,否则,它会查找隐式 Nothing
,显然找不到。生成的错误消息非常冗长且无用,因此我输入了一条自定义错误消息。请注意,这不会检查是否有重复的键,但可以通过简单的修改来做到这一点。
不幸的是,我发现自己必须在使用站点上明确注释键值对的类型:
//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)
)
)
给定以下枚举:
enum Connector:
case CHAdeMO
case Mennekes
case CCS
case Tesla
是否有类似 Map[ConnectorType, Int]
但会产生编译错误的类型:
Map(
Connector.CHAdeMO -> 1,
Connector.Mennekes -> 2,
Connector.CCS -> 3,
)
即地图不包含 Connector.Tesla
的键。换句话说,在编译时类型应该类似于 ((Connector.CHAdeMO, Int), (Connector.Mennekes, Int), (Connector.CCS, Int), (Connector.Tesla, Int))
但在其他方面表现得像一个普通的 Map 。
这是一个使用元组的解决方案 (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
这将采用元组 (Connector, Int)
的元组,并检查它是否包含所有 Connector
类型的另一个元组中的所有类型。如果输入至少包含一次所有连接器类型,它会查找隐式 DummyImplicit
,否则,它会查找隐式 Nothing
,显然找不到。生成的错误消息非常冗长且无用,因此我输入了一条自定义错误消息。请注意,这不会检查是否有重复的键,但可以通过简单的修改来做到这一点。
不幸的是,我发现自己必须在使用站点上明确注释键值对的类型:
//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)
)
)