Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Mirror.Sum for union types #21

Open
bilal-fazlani opened this issue Jun 23, 2021 · 6 comments
Open

Support Mirror.Sum for union types #21

bilal-fazlani opened this issue Jun 23, 2021 · 6 comments
Labels
enhancement New feature or request

Comments

@bilal-fazlani
Copy link

type Color = "Brown" | "White" | "Yellow" | "Black"
given Encoder[Color] = Encoder.derived
inline def derived[T](using gen: K0.Generic[T]): Encoder[T] =
  gen.derive(product, coproduct)
[error] 19 |  given Encoder[Color] = Encoder.derived
[error]    |                                        ^
[error]    |     no implicit argument of type shapeless3.deriving.K0.Generic[
[error]    |       (("Brown" : String) | ("White" : String) | ("Yellow" : String) |
[error]    |         ("Black" : String)
[error]    |       )
[error]    |     ] was found for parameter gen of method derived in object Encoder
@milessabin
Copy link
Member

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 Mirrors for these so this would most likely need macro-support of some form.

@milessabin milessabin added the enhancement New feature or request label Jun 23, 2021
@bilal-fazlani
Copy link
Author

Thanks!
Sorry if this was obvious. I have just started learning shapeless.

@bilal-fazlani bilal-fazlani changed the title Error when trying to derive a typeclass instance for a Union type of literal types Error when trying to derive a typeclass instance for a Union type Jun 23, 2021
@iRevive
Copy link

iRevive commented Oct 5, 2021

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

@milessabin
Copy link
Member

@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.

@iRevive
Copy link

iRevive commented Oct 21, 2021

@milessabin thank you for the feedback.

I experimented a bit with the Mirror. The example is available here https://scastie.scala-lang.org/k3VLtxUTTie18YodrQKtJw.

Mirror.SumOf

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:
image

Alias for Mirror.Sum with full type info

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

Next steps

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] = ...
  }
}

@joroKr21
Copy link
Member

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.

@joroKr21 joroKr21 changed the title Error when trying to derive a typeclass instance for a Union type Support Mirror.Sum for union types Apr 15, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants