Open bilal-fazlani opened 3 years ago
Thanks for the report ... shapeless 3 doesn't currently support union types.
In principle support could be added. We would have to support the creation of CoproductGeneric
instances for union types. There are no Mirror
s for these so this would most likely need macro-support of some form.
Thanks! Sorry if this was obvious. I have just started learning shapeless.
I've been experimenting with Union types recently.
Perhaps some samples can be reused in shapeless-3:
Actually, it would be nice to have this logic as a part of the Scala compiler (perhaps as a part of the Mirror).
@iRevive nice! Do you think that can be generalized for kinds other than *
?
Bear in mind that Mirror
is basically just a type class, albeit one with compiler support for generating a specific sort of instance. There's nothing to stop an external macro from generating instances for other types.
If you were interested in exploring that space I'd be super keen to see that in shapeless 3.
@milessabin thank you for the feedback.
I experimented a bit with the Mirror. The example is available here https://scastie.scala-lang.org/k3VLtxUTTie18YodrQKtJw.
In the example below the compiler loses type information of the MirroredElemTypes
type. It's resolved only as Tuple
, instead of a proper type.
given unionMirror: Mirror.SumOf[Int | String | Long] = new Mirror.Sum {
type MirroredType = Int | String | Long
type MirroredMonoType = Int | String | Long
type MirroredLabel = "Int | String | Long"
type MirroredElemTypes = Int *: String *: Long *: EmptyTuple
type MirroredElemLabels = Tuple3["Int", "String", "Long"]
def ordinal(x: MirroredMonoType): Int = x match {
case _: Int => 0
case _: String => 1
case _: Long => 2
}
}
summon[Show[Int | String | Long]].show("string-value") // does not compile
// Error:
// cannot reduce inline match with
// scrutinee: scala.compiletime.erasedValue[Playground.unionMirror.MirroredElemTypes] : Playground.unionMirror.MirroredElemTypes
// patterns : case _:EmptyTuple
// case _:*:[t @ _, ts @ _]
This is how the compiler resolves the MirroredElemTypes
(selType variable) at this breakpoint:
When Mirror.SumOf
is replaced with a new type alias (with explicit underlying types), the derived[A](using m: Mirror.Of[A])
method picks up the given value and the code compiles.
type MirrorUnion[TPE, MET, MEL] = Mirror.Sum {
type MirroredType = TPE
type MirroredMonoType = TPE
type MirroredElemTypes = MET
type MirroredElemLabels = MEL
}
given unionMirror: MirrorUnion[Int | String | Long, Int *: String *: Long *: EmptyTuple, ("Int", "String", "Long")] =
new Mirror.Sum {
//body the same as above
}
summon[Show[Int | String | Long]].show("string-value") // compiles
I assume it's expected behavior, since Mirror.SumOf
clearly says that MirroredElemTypes
is only a subtype of Tuple
, and the real type is not known at this stage.
type SumOf[T] = Mirror.Sum { type MirroredType = T; type MirroredMonoType = T; type MirroredElemTypes <: Tuple }
Currently, I do not understand how the macro can be defined, since the result type should be known. But both MirroredElemTypes
and MirroredElemLabels
can be decided only during compilation.
I tried the following trick, but the compiler does not like it:
object UnionMirror {
// does not compile with the following error:
// access to parameter u from wrong staging level:
// - the definition is at level 0,
// - but the access is at level -1.
inline given derived[A](using u: UnionInfo[A]): u.Mirror = ${ deriveImpl[A] }
def deriveImpl[A: Type](using quotes: Quotes, u: UnionInfo[A]): Expr[u.Mirror] = {
???
}
trait UnionInfo[A] {
type MirroredElemTypes
type MirroredElemLabels
type Mirror = Mirror.Sum {
type MirroredType = A
type MirroredMonoType = A
type MirroredElemTypes = self.MirroredElemTypes
type MirroredElemLabels = self.MirroredElemLabels
}
}
object UnionInfo {
inline given derived[A]: UnionInfo[A] = ...
}
}
The problem with your manual given
is that you fixed the type without the refinement. You could try given Mirror.Sum with
instead. As for the macro - I think transparent inline
should work because it preserves the narrow type.