2

I have a case class with two options

case class MyClass(
  first: Option[String] = None,
  second: Option[Boolean] = None
)

Is there a way in Scala to specify at the type level that both first and second can't both be set? So if MyClass.second.isDefined then MyCLass.first must be None and vice versa, or both can be None.

This is possible in TypeScript.

Boris Verkhovskiy
  • 14,854
  • 11
  • 100
  • 103
  • Instead of two `Option` properties, rather have a single `Either` one. – Luis Miguel Mejía Suárez May 27 '22 at 22:44
  • @LuisMiguelMejíaSuárez so you're saying this is not possible? What if I have 3 (or more) options of 3 different types, of which only 1 can be set? – Boris Verkhovskiy May 27 '22 at 22:51
  • 1
    No, it's not possible. If you have more fields then a custom ADT is usually the best way to model this. – Luis Miguel Mejía Suárez May 27 '22 at 22:59
  • Well, **Scala 3** has union types and safer nulls so you may combine both to emulate the **typescript** solution. But note that is different from what you asked, still in **typescript** there is no way to say that a single type will only have one set value, rather you mix multiple different types into one... which is not conceptually different from the custom ADT that I proposed. – Luis Miguel Mejía Suárez May 27 '22 at 23:13

1 Answers1

1

So, how about this (the following code is for scala3, but it is easy to implement in scala2):

scala> @annotation.implicitNotFound("Need (None, Some) or (Some, None), found (${F}, ${S})")
     | trait Cons[F, S]
     |
     | object Cons:
     |   given Cons[Some[String], None.type]  = new Cons {}
     |   given Cons[None.type, Some[Boolean]] = new Cons {}
     |
     | case class MyClass[F <: Option[String], S <: Option[Boolean]](first: F, second: S)(using c: Cons[F, S])

scala> val m1 = MyClass(None, Some(true))
val m1: MyClass[None.type, Some[Boolean]] = MyClass(None,Some(true))

scala> val m2 = MyClass(Some(""), None)
val m2: MyClass[Some[String], None.type] = MyClass(Some(),None)

scala> val m3 = MyClass(None, None)
-- Error: --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 |val m3 = MyClass(None, None)
  |                            ^
  |                            Need (None, Some) or (Some, None), found (None.type, None.type)
1 error found

scala> val m4 = MyClass(Some(""), Some(false))
-- Error: --------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1 |val m4 = MyClass(Some(""), Some(false))
  |                                       ^
  |                                       Need (None, Some) or (Some, None), found (Some[String], Some[Boolean])
1 error found
Eastsun
  • 18,526
  • 6
  • 57
  • 81