编译时保证地图对每个枚举案例都有一个键

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)
  )
)