I have a wrapper that may contain heterogeneous elements
I can express
val c: Container[A | B | C] = Container(A("hi"), B(100), C(3.5))
Note:
They descend from same base class, if that’s relevant
Only one instance of each type is ever allowed in the container even if values are different. Container(B(3), B(10)) will throw an error
I need to implement a without[X](x: X) operator that produces a copy of the container without the instance of the type X.
This singular method must work for arbitrary types, no hardcoding withoutA(), withoutB() etc.
val c: Container[A | B | C] = Container(A("hi"), B(100), C(3.5))
c.without(B()) // b: Container[A | C]
c.without(B(40)) // b: Container[A | C]
// does not matter that original B stored 100. Remove whatever B is found.
// this is better than needing to do a.without(classOf[B])
c.without(D()) // no need to worry about this,
// the compiler should alert error on it
// because `D` was not part of the original union.
This could be done in TS using the Omit type. Something like
trait Base
case class A(s: String = "") extends Base
case class B(n: Int = 0) extends Base
case class C(d: Double = 0.0) extends Base
case class D() extends Base
// more...
class Container[T <: Base](elements: T*):
val set: Set[T] = elements.toSet
// type Omit[X, Y] = ...
def without[K <: T](k: K): Container[Omit[K, T]] =
Container(
elements.filter(_.isInstanceOf[K] == false)
).asInstanceOf
You can: either the type alias type Omit[X, Y] = X, or the type lambdatype Omit = [X, Y] =>> X but there is no simple clear way to say “if this type is in the form of a union X | Y then…”. Even match types cannot do this as far as I can tell; since they need to match on concrete types, not type parameters.
Keep in mind Scala’s union types are much more limited. Think before you base your entire design on unions.
It is technically possible to almost do something like this by using tuples to keep track of explicit types. It’s a lot of work. The compiler doesn’t really do flow-based typing for you, so you can’t, for instance, match out one of a sealed type and get from the rest of the match the union of all the other types. (The compiler does keep track of this for completeness, but the language doesn’t supply it to you as a type union.)
Scala apparently has no way to say “remove X type from Y” in any context whether that is in unions or tuple concatenations etc. Once you have gone from Set[A | B] -> Set[A | B | C], it is impossible to go back to Set[A | B]. You cannot express Set[A | B | C] -> Set[A | B]. I can easily remove all C instances from a Set in the runtime, but I can’t express that this has happened on the typelevel.
I don’t need flow-typing or pattern matching, I just need a return type operator I can use on a function signature to express “everything as the same but simply remove K”.
Can this be done with a macro? A macro that looks at the current union type, and simply rewrites the entire union but without C behind the scenes? If we can’t remove K from T, can we rebuild all of T without K?
Although there is not a “built in” way to remove an element from a NamedTuple (maybe one arrives in future!), I think this implementation, more or less does what you want, in that it splits a named Tuple at some name, drops the Tuple element element at the corresponding index, and rebuilds a new NamedTuple with the Names (excluding the missing one)
The type tests are here.
And it looks transparent to the caller. The .instanceOf is unsatisfying, but .
So I think this can be done with NamedTuples. Whether this is a good idea or not, is then up to you :-).
As things stand it’s held up to my own usage. YMMV.