I am trying to find a way, in shapeless, to prove that a given product type extends no sealed trait, and hence does not belong to any coproduct. Given the following sealed trait hierarchy:
sealed trait Foo
case class Bar(a: Char) extends Foo
case class Baz(b: Int) extends Foo
I know I can use shapeless.ops.coproduct.Basis to prove that a given choice, or subsequence of choices, belongs to a co-product. Eg:
import shapeless._
type L = Bar :+: Baz :+: CNil
implicitly[ops.coproduct.Basis[L, Bar :+: CNil]]
What I am after now, is an operation to get the coproduct from a choice. Eg. given Bar or Baz, I would like to get back L, or alternatively, the type of the sealed base trait Foo.
Is this something that shapeless can do? alternatively, is it possible to do it with macros?
Update:
I ended up writing a fairly involved implicit macro...
import scala.language.experimental.macros
import scala.reflect.macros.whitebox
trait SealedBaseTraitOf[A] {
type Repr
}
object SealedBaseTraitOf {
def materializeImpl[A](c: whitebox.Context)(implicit tag: c.WeakTypeTag[A]): c.Expr[SealedBaseTraitOf[A]] = {
import c.universe._
val a = weakTypeOf[A].typeSymbol
val baseClasses = a.asClass.baseClasses
val maybeBaseTrait =
baseClasses
.find(t => t.asClass.isTrait && t.asClass.isSealed)
.map(_.asType.name)
val repr = maybeBaseTrait.map(t => tq"$t").getOrElse(tq"Nothing")
println(s"Got a repr: $repr")
c.Expr[SealedBaseTraitOf[A]](q"""
new SealedBaseTraitOf[$a] {
type Repr = $repr
}
""")
}
implicit def materialize[A]:SealedBaseTraitOf[A] = macro materializeImpl[A]
}
Putting all together, the macro can be used as follows:
case class ExtendsNothing(a: Int)
sealed trait X
case class ExtendsX(b: Char) extends X
import shapeless._
val bt1 = the[SealedBaseTraitOf[ExtendsNothing]]
implicitly[bt1.Repr =:= Nothing]
val bt2 = the[SealedBaseTraitOf[ExtendsX]]
implicitly[bt2.Repr =:= X]
val coprodX = Generic[X]
val coprodBt2 = Generic[bt2.Repr]
implicitly[coprodX.Repr =:= coprodBt2.Repr]
implicitly[ops.coproduct.Basis[coprodBt2.Repr, ExtendsX :+: CNil]]
While getting close to a solution, I am still hoping to find something a bit less involved, possibly which doesn't involve using macros.
Related
I have a singleton objet with 100 different case classes. For example:
object Foo {
case class Bar1 {
...
}
...
case class Bar100 {
...
}
}
I would like to be able to iterate over each of the case class.
Something like getting all the case classes in a Seq and then being able to map over it.
(map with a polymorphic function for example)
Is it possible using reflection? If yes how? And what are the drawbacks of using reflection here over hard coding a sequence with all the case classes.
Foo.getClass.getDeclaredClasses gives you all the classes declared inside Foo. Because they are case classes, each also defines a companion object (which is also a class), so you'll need to filter them out:
Foo.getClass.getDeclaredClasses.filterNot(_.endsWith("$"))
Reflection is slow, but if you are only going to do it once (no reason to do it more than once, because you'll alway be getting the same result), it's not really an issue.
A bigger problem is that I can't really imagine a "polymorphic function" that would let you do anything useful with this information without some extreme hacking.
Fully parametric polymorphic functions exist in Scala 3. In Scala 2 there are no parametric polymorphic functions (and polymorphic values at all, only polymorphic methods) but there are ad-hoc polymorphic functions implemented normally with Shapeless.
I guess in Shapeless there is a type class (Generic/LabelledGeneric) for iterating case classes extending some sealed trait
case class Bar1() extends MyTrait
//...
case class Bar100() extends MyTrait
Scala how to derivate a type class on a trait
Use the lowest subtype in a typeclass?
Type class instance for case objects defined in sealed trait
Iteration over a sealed trait in Scala?
Getting subclasses of a sealed trait
Can I get a compile-time list of all of the case objects which derive from a sealed parent in Scala?
but not for iterating case classes nested into an object. So probably we'd need a macro (compile-time reflection) anyway, even using Shapeless
import scala.language.experimental.macros
// libraryDependencies += scalaOrganization.value % "scala-reflect" % scalaVersion.value
import scala.reflect.macros.whitebox
// libraryDependencies += "com.chuusai" %% "shapeless" % "2.3.10"
import shapeless.HList
trait GetInnerCaseClasses[A] {
type Out <: HList
}
object GetInnerCaseClasses {
type Aux[A, Out0] = GetInnerCaseClasses[A] { type Out = Out0 }
implicit def mkGetInnerCaseClasses[A, Out <: HList]: Aux[A, Out] =
macro mkGetInnerCaseClassesImpl[A]
def mkGetInnerCaseClassesImpl[A: c.WeakTypeTag](c: whitebox.Context): c.Tree = {
import c.universe._
val A = weakTypeOf[A]
val caseClasses = A.decls.filter(s => s.isClass && s.asClass.isCaseClass)
val hList = caseClasses.foldRight[Tree](tq"_root_.shapeless.HNil")(
(s, hl) => tq"_root_.shapeless.::[$s, $hl]"
)
q"""
new GetInnerCaseClasses[$A] {
override type Out = $hList
}
"""
}
}
// in a different subproject
import shapeless.ops.hlist.{FillWith, Mapper}
import shapeless.{::, HList, HNil, Poly0, Poly1, Typeable, the}
object Foo {
case class Bar1()
// ...
case class Bar100()
}
implicitly[GetInnerCaseClasses.Aux[Foo.type, Bar1 :: Bar2 :: Bar100 :: HNil]] // compiles
val gicc = the[GetInnerCaseClasses[Foo.type]] // "the" is an advanced version of "implicitly" not damaging type refinements
implicitly[gicc.Out =:= (Bar1 :: Bar2 :: Bar100 :: HNil)] // compiles
// I'm just printing names of case classes, you should replace this with your actual iterating logic
object myPoly extends Poly1 {
implicit def cse[A <: Product : Typeable]: Case.Aux[A, Unit] =
at(_ => println(Typeable[A].describe))
}
object nullPoly extends Poly0 {
implicit def cse[A]: Case0[A] = at(null.asInstanceOf[A])
}
def myIterate[A <: Singleton] = new PartiallyAppliedMyIterate[A]
class PartiallyAppliedMyIterate[A <: Singleton] {
def apply[L <: HList]()(implicit
getInnerCaseClasses: GetInnerCaseClasses.Aux[A, L],
mapper: Mapper[myPoly.type, L],
fillWith: FillWith[nullPoly.type, L] // because type class GetInnerCaseClasses works only on type level
): Unit = mapper(fillWith())
}
myIterate[Foo.type]()
// Bar1
// ...
// Bar100
I have a code
case class MyTypeTag[T] ()
def getTypeTags[TT <: Product : TypeTag] = {
val subtypesTags: List[MyTypeTag[Option[_]] = ???
sybtypesTags
}
val res = getTypeTags[(Int, String, Boolean)]
// res = MyTypeTag[Option[Int]] :: MyTypeTag[Option[String]] :: MyTypeTag[Option[Boolean]] :: Nil
so i want to call function getTypeTags passing any tuple type as type parameter and get list of MyTypeTag instances with each type inside tuple wrapped in Option
if in intelliJ i evaluate expression typeof[TT] i see property args with list of my types, but i do not know how to access from code. Or may be some other ways can be apllied.
Thanks in advance
Type parameter T in case class MyTypeTag[T]() must be known at compile time. But it seems you try to define it using runtime reflection. This can't work (unless you define the class at runtime: toolbox.define(q"case class MyTypeTag[T]()") but this would be tricky).
You can try to use compile-time reflection
import scala.language.experimental.macros
import scala.reflect.macros.blackbox
def getTypeTags[TT <: Product]: List[MyTypeTag[_ <: Option[_]]] =
macro getTypeTagsImpl[TT]
def getTypeTagsImpl[TT: c.WeakTypeTag](c: blackbox.Context): c.Tree = {
import c.universe._
weakTypeOf[TT].typeArgs.map(t => q"MyTypeTag[Option[$t]]()")
.foldRight[Tree](q"Nil")((t, ts) => q"$t :: $ts")
}
Usage:
getTypeTags[(Int, String, Boolean)] //List(MyTypeTag(), MyTypeTag(), MyTypeTag())
In order to verify that the code works properly let's temporarily modify MyTypeTag
import scala.reflect.runtime.universe.TypeTag
case class MyTypeTag[T]()(implicit val typeTag: TypeTag[T])
val res = getTypeTags[(Int, String, Boolean)]
res(0).typeTag // TypeTag[Option[Int]]
res(1).typeTag // TypeTag[Option[String]]
res(2).typeTag // TypeTag[Option[Boolean]]
You can also use Shapeless instead of macros
import shapeless.ops.hlist.{FillWith, Mapped, ToList}
import shapeless.{Generic, HList, Poly0}
case class MyTypeTag[T]()
def getTypeTags[TT <: Product] = new {
def apply[L <: HList, L1 <: HList]()(implicit
generic: Generic.Aux[TT, L],
mapped: Mapped.Aux[L, λ[X => MyTypeTag[Option[X]]], L1],
fillWith: FillWith[myTypeTagPoly.type, L1],
toList: ToList[L1, MyTypeTag[_ <: Option[_]]]
): List[MyTypeTag[_ <: Option[_]]] =
fillWith().toList
}
object myTypeTagPoly extends Poly0 {
implicit def cse[A]: Case0[MyTypeTag[Option[A]]] = at(MyTypeTag[Option[A]]())
}
getTypeTags[(Int, String, Boolean)]() // List(MyTypeTag(), MyTypeTag(), MyTypeTag())
If you make MyTypeTag covariant (MyTypeTag[+T]) then List[MyTypeTag[_ <: Option[_]]] can be replaced with List[MyTypeTag[Option[_]]].
You can't distinguish between an instance of MyTypeTag[Int] and an instance of MyTypeTag[String] at runtime (to check this for yourself, try
val l = List(MyTypeTag[Option[Int]](), MyTypeTag[Option[String]](), MyTypeTag[Option[Boolean]]())
and see what that gives you, and what you can and can't do with it), so the answer to the question as you ask it is
def getTypeTags[TT <: Product](implicit tt: TypeTag[TT]): List[MyTypeTag[_]] = {
tt.tpe.typeParams.map(_ => MyTypeTag[Option[_]]())
}
You can get the type parameters with tt.tpe.typeParams, but since that's a runtime value, you can't recover that as a compile-time type T for MyTypeTag[T] since it doesn't exist at compile-time yet.
Maybe you can leverage shapeless to do whatever it is you want to do, it has ways to abstract over tuples. See https://underscore.io/books/shapeless-guide/
Suppose the elements of an HList are subclasses of a generic trait. Each element is contained in case class Box[E](elem E). That Box is invariant in E causes problems with mapping a poly1 over HList, selecting an element by its parent trait, etc. Here's an example:
import shapeless._
trait Drink[+A]{ def v: A}
case class Water(v: Int) extends Drink[Int]
case class Juice(v: BigDecimal) extends Drink[BigDecimal]
case class Squash(v: BigDecimal) extends Drink[BigDecimal]
case class Box[E](elem: E) // NB! invariance in E
object pour extends Poly1{
implicit def caseInt[A <: Box[Drink[Int]]] = at[A](o => Box(o.elem.v * 2))
implicit def caseDec[A <: Box[Drink[BigDecimal]]] = at[A](o => Box(o.elem.v + 5.0))
}
object Proc {
type I = Box[Water] :: Box[Squash] :: Box[Juice] :: HNil
type O = Box[Int] :: Box[BigDecimal] :: Box[BigDecimal] :: HNil
val drinks: I = Box(Water(10)) :: Box(Squash(15.0)) :: Box(Juice(2.0)) :: HNil
def make()(implicit m: ops.hlist.Mapper.Aux[pour.type, I, O]): O = drinks.map(pour)
}
object Main extends App{
override def main(args: Array[String]): Unit = Proc.make()
}
*The function pour applies the answer by #Jasper_M to Mapping over HList with subclasses of a generic trait.
This code leads to
Error:(38, 22) could not find implicit value for parameter m: shapeless.ops.hlist.Mapper.Aux[pour.type,Proc.I,Proc.O]
Proc.make().
Also, filtering Proc.drinks.covariantFilter[Box[Drink[Int]]] produces HNil. (This filter implements the answer by #Travis Brown to Do a covariant filter on an HList.)
Defining Box[+E], which solves the problems, is not possible in my project. A naive solution -- to have a case in the pour for each subclass of Drink -- does not scale. (This could be made to work by passing monomorphic functions to pour, which I don't know how.)
Could there be a more sensible approach to mapping or filtering over HLists in this set-up?
In this case where all your outer type constructors are Box, you can apply almost the same technique as in my previous answer:
object pour extends Poly1{
implicit def caseInt[A <: Drink[Int]] = at[Box[A]](o => Box(o.elem.v * 2))
implicit def caseDec[A <: Drink[BigDecimal]] = at[Box[A]](o => Box(o.elem.v + 5.0))
}
Now if your type of Box is also polymorphic, you can still go a step further:
import shapeless._
trait Drink[+A]{ def v: A}
case class Water(v: Int) extends Drink[Int]
case class Juice(v: BigDecimal) extends Drink[BigDecimal]
case class Squash(v: BigDecimal) extends Drink[BigDecimal]
trait Box[E] { def elem: E}
case class ABox[E](elem: E) extends Box[E]
case class BBox[E](elem: E) extends Box[E]
object pour extends Poly1{
implicit def caseInt[A <: Drink[Int], M[x] <: Box[x]] = at[M[A]](o => o.elem.v * 2)
implicit def caseDec[A <: Drink[BigDecimal], M[x] <: Box[x]] = at[M[A]](o => o.elem.v + 5.0)
}
val drinks = ABox(Water(10)) :: BBox(Squash(15.0)) :: ABox(Juice(2.0)) :: HNil
drinks.map(pour)
You may have noticed I didn't re-wrap the values in its box in this last example. You could still do that, for instance if you implement something like a trait Boxer[M[_]] { def box[A](a: A): M[A] } typeclass, or with F-bounded polymorphism in Box, but that would probably lead us too far.
I'm using sealed traits as enums for exhaustive pattern matching. In cases where I have case objects instead of case classes extending my trait, I'd like to encode and decode (via Circe) as just a plain string.
For example:
sealed trait State
case object On extends State
case object Off extends State
val a: State = State.Off
a.asJson.noSpaces // trying for "Off"
decode[State]("On") // should be State.On
I understand that this will be configurable in 0.5.0, but can anyone help me write something to tide me over until that's released?
To highlight the problem—assuming this ADT:
sealed trait State
case object On extends State
case object Off extends State
circe's generic derivation will (currently) produce the following encodings:
scala> import io.circe.generic.auto._, io.circe.syntax._
import io.circe.generic.auto._
import io.circe.syntax._
scala> On.asJson.noSpaces
res0: String = {}
scala> (On: State).asJson.noSpaces
res1: String = {"On":{}}
This is because the generic derivation mechanism is built on Shapeless's LabelledGeneric, which represents case objects as empty HLists. This will probably always be the default behavior, since it's clean, simple, and consistent, but it's not always what you want (as you note the configuration options that are coming soon will support alternatives).
You can override this behavior by providing your own generic instances for case objects:
import io.circe.Encoder
import shapeless.{ Generic, HNil }
implicit def encodeCaseObject[A <: Product](implicit
gen: Generic.Aux[A, HNil]
): Encoder[A] = Encoder[String].contramap[A](_.productPrefix)
This says, "if the generic representation of A is an empty HList, encode it as its name as a JSON string". And it works as we'd expect for case objects that are statically typed as themselves:
scala> On.asJson.noSpaces
res2: String = "On"
When the value is statically typed as the base type, the story is a little different:
scala> (On: State).asJson.noSpaces
res3: String = {"On":"On"}
We get a generically derived instance for State, and it respects our manually defined generic instance for case objects, but it still wraps them in an object. This makes some sense if you think about it—the ADT could contain case classes, which can only reasonably be represented as a JSON object, and so the object-wrapper-with-constructor-name-key approach is arguably the most reasonable thing to do.
It's not the only thing we can do, though, since we do know statically whether the ADT contains case classes or only case objects. First we need a new type class that witnesses that an ADT is made up only of case objects (note that I'm assuming a fresh start here, but it should be possible to make this work alongside generic derivation):
import shapeless._
import shapeless.labelled.{ FieldType, field }
trait IsEnum[C <: Coproduct] {
def to(c: C): String
def from(s: String): Option[C]
}
object IsEnum {
implicit val cnilIsEnum: IsEnum[CNil] = new IsEnum[CNil] {
def to(c: CNil): String = sys.error("Impossible")
def from(s: String): Option[CNil] = None
}
implicit def cconsIsEnum[K <: Symbol, H <: Product, T <: Coproduct](implicit
witK: Witness.Aux[K],
witH: Witness.Aux[H],
gen: Generic.Aux[H, HNil],
tie: IsEnum[T]
): IsEnum[FieldType[K, H] :+: T] = new IsEnum[FieldType[K, H] :+: T] {
def to(c: FieldType[K, H] :+: T): String = c match {
case Inl(h) => witK.value.name
case Inr(t) => tie.to(t)
}
def from(s: String): Option[FieldType[K, H] :+: T] =
if (s == witK.value.name) Some(Inl(field[K](witH.value)))
else tie.from(s).map(Inr(_))
}
}
And then our generic Encoder instances:
import io.circe.Encoder
implicit def encodeEnum[A, C <: Coproduct](implicit
gen: LabelledGeneric.Aux[A, C],
rie: IsEnum[C]
): Encoder[A] = Encoder[String].contramap[A](a => rie.to(gen.to(a)))
Might as well go ahead and write the decoder too.
import cats.data.Xor, io.circe.Decoder
implicit def decodeEnum[A, C <: Coproduct](implicit
gen: LabelledGeneric.Aux[A, C],
rie: IsEnum[C]
): Decoder[A] = Decoder[String].emap { s =>
Xor.fromOption(rie.from(s).map(gen.from), "enum")
}
And then:
scala> import io.circe.jawn.decode
import io.circe.jawn.decode
scala> import io.circe.syntax._
import io.circe.syntax._
scala> (On: State).asJson.noSpaces
res0: String = "On"
scala> (Off: State).asJson.noSpaces
res1: String = "Off"
scala> decode[State](""""On"""")
res2: cats.data.Xor[io.circe.Error,State] = Right(On)
scala> decode[State](""""Off"""")
res3: cats.data.Xor[io.circe.Error,State] = Right(Off)
Which is what we wanted.
I have the simplified situation:
abstract sealed trait Top
class A[T] extends Top
class B[T] extends Top
class Typeclass[T]
implicit def a[T] = new Typeclass[A[T]]
implicit def b[T] = new Typeclass[B[T]]
Now I have a Map[String, Top] and want to use an operation on all values in the map that require the presence of an instance of Typeclass to be available in the context. This will not compile as the concrete types of the values in the map are not visible from its type and I can therefore not set a context bound for them.
Is there a way to tell the compiler that in fact there will always be an instance available? In this example this is given as there are implicit functions to generate those instances for every concrete subtype of Top.
Or is the only solution to use a HList and recurse over its type requiring all the instances to be in context?
I recommend using some variation on this adaptation of Oleg's Existentials as universals in this sort of situation ... pack the the type class instance along with the value it's the instance for,
abstract sealed trait Top
class A[T] extends Top
class B[T] extends Top
class Typeclass[T]
implicit def a[T] = new Typeclass[A[T]]
implicit def b[T] = new Typeclass[B[T]]
trait Pack {
type T <: Top
val top: T
implicit val tc: Typeclass[T]
}
object Pack {
def apply[T0 <: Top](t0: T0)(implicit tc0: Typeclass[T0]): Pack =
new Pack { type T = T0 ; val top = t0 ; val tc = tc0 }
}
val m = Map("a" -> Pack(new A[Int]), "b" -> Pack(new B[Double]))
def foo[T: Typeclass](t: T): Unit = ()
def bar(m: Map[String, Pack], k: String): Unit =
m.get(k).map { pack =>
import pack._ // imports T, top and implicit tc
foo(top) // instance available for call of foo
}
bar(m, "a")
As discussed in comment it would be more convenient to have the typeclass defined on Top, and it might be done with pattern matching.
supposing part of the definition of the typeclass is
def f[T](t: T): FResult[T],
and you have the corresponding implentations
def fOnA[T](t: A[T]): FResult[A[T]] = ...
def fOnB[T](t: B[T]): FResult[B[T]] = ...
Then you can define
def fOnT(t: Top) : FResult[Top] = t match {
case a: A[_] => fOnA(a)
// provided an FResult[A[T]] is an FResult[Top],
// or some conversion is possible
case b: B[_] => fOnB(b)
}
If is both legal and safe to call a generic method, such as fOnA[T] with an existential (a matching A[_])
However, it might be difficult to convince the compiler that the parameter you pass to f or the result you get are ok, given the reduced information of the existential. If so, please post the signatures you need.