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

I'm trying to create a map of different configurations, where each configuration has a given key object and some options, e.g.

  • FirstConfig can be either:
    • FirstConfigOptionA
    • FirstConfigOptionB
  • SecondConfig can be either:
    • SecondConfigOptionA
    • SecondConfigOptionB
  • ...

And I'm having trouble with general typing and signature of the setter function so it checks at compile time I'm supplying the correct objects, e.g.

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

So far, my attempts still allow the second case above to compile.

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

I've also tried using Enumerations with similar results:

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

What is the correct way to express this relationship between a config and its available options or what should be set's signature to enforce it?



Solution 1:[1]

Why do you need to store them as key/value pairs? You can just represent it as an algebraic data type:

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 = …

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Matthias Berndt