如何根据 Scala 中的另一个参数限制泛型参数的参数类型?

How do I limit the parameter types of a generic argument based on another parameter in Scala?

我正在尝试创建不同配置的映射,其中每个配置都有一个给定的关键对象和一些选项,例如

而且我在 setter 函数的一般输入和签名方面遇到了问题,因此它会在编译时检查我是否提供了正确的对象,例如

// 1. this should compile normally
set(FirstConfig, FirstConfigOptionA)

// 2. should NOT compile due to `SecondConfigOptionA` parameter not being a valid option for `FirstConfig`
set(FirstConfig, SecondConfigOptionA)

到目前为止,我的尝试仍然允许上面的第二种情况编译。

abstract sealed class Configuration
trait OptionKey[T <: Configuration] {}
trait OptionVariant[T <: Configuration] {}

// First Config
trait FirstConfig extends Configuration
object FirstConfigKey extends OptionKey[FirstConfig];
object FirstConfigOptionA extends OptionVariant[FirstConfig]
object FirstConfigOptionB extends OptionVariant[FirstConfig]

// Second Config
trait SecondConfig extends Configuration
object SecondConfigKey extends OptionKey[SecondConfig];
object SecondConfigOptionA extends OptionVariant[SecondConfig]
object SecondConfigOptionB extends OptionVariant[SecondConfig]

def set[T](k: OptionKey[T], v: OptionVariant[T]): Unit = {}

set(FirstConfigKey, FirstConfigOptionA)
set(FirstConfigKey, SecondConfigOptionA) // This still compiles

我也试过使用枚举得到类似的结果:

object FirstConfig extends Enumeration {
  type FirstConfig = Value
  val FirstConfigOptionA, FirstConfigOptionB = Value
}

object SecondConfig extends Enumeration {
  type SecondConfig = Value
  val SecondConfigOptionA, SecondConfigOptionB = Value
}

def set(k: Enumeration, v: Enumeration#Value): Unit = {}

set(FirstConfig, FirstConfig.FirstConfigOptionA)
set(FirstConfig, SecondConfig.SecondConfigOptionA) // This still compiles

表达配置与其可用选项之间这种关系的正确方法是什么,或者 set 的签名应该是什么来执行它?

使用 path-dependant 这样的类型怎么样:

trait Configuration {
  sealed trait OptionKey
  val key: OptionKey
  
  sealed trait OptionVariant
}

object Configuration {
  def set(config: Configuration)(variant: config.OptionVariant): Unit = {
    println(s"${config} - ${config.key} - ${variant}")
  }
}

case object FirstConfig extends Configuration {
  private case object FirstConfigKey extends OptionKey
  override final val key: OptionKey = FirstConfigKey
  
  case object FirstConfigOptionA extends OptionVariant
  case object FirstConfigOptionB extends OptionVariant
}

case object SecondConfig extends Configuration {
  private case object SecondConfigKey extends OptionKey
  override final val key: OptionKey = SecondConfigKey
  
  case object SecondConfigOptionA extends OptionVariant
  case object SecondConfigOptionB extends OptionVariant
}

您可以看到它按预期工作here

为什么需要将它们存储为 key/value 对?您可以将其表示为代数数据类型:

enum FirstConfig:
  case OptionA
  case OptionB

enum SecondConfig:
  case OptionA
  case OptionB

enum Config:
  case First(value: FirstConfig)
  case Second(value: SecondConfig)

def set(config: Config): Unit = …