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.
Related
I currently have the following (not type safe) api which I'm trying to redesign in a typesafe way:
import cats.instances.list._
import cats.syntax.functorFilter._
sealed trait EnumType
case object A extends EnumType
case object B extends EnumType
case object C extends EnumType
sealed abstract class TypeInfo[T <: EnumType](val enumType: T)
case class Ainfo() extends TypeInfo(A)
case class Ainfo2() extends TypeInfo(A)
case class Binfo() extends TypeInfo(B)
case class Cinfo() extends TypeInfo(C)
//This is the function implemented in a not typesafe way
def filterByEnumType[T <: EnumType: ClassTag](lst: List[TypeInfo[_]]): List[TypeInfo[T]] = {
lst mapFilter { info =>
info.enumType match {
case _: T => Some(info.asInstanceOf[TypeInfo[T]]) //not type safe
case _ => None
}
}
}
filterByEnumType[A.type](List(Ainfo(), Binfo(), Ainfo2(), Cinfo())) //List(Ainfo(), Ainfo2())
Is there an approach to implement it typesafely? Are typemembers useful for such task or probbably shapeless can be used for that?
I came up with two related approaches with shapeless. I'm not sure either will exactly meet your needs because they depend on having all the types of the elements of the list known ahead of time.
Assuming you have this stuff:
import shapeless._
import shapeless.ops.hlist._
type HType = TypeInfo[A.type] :: TypeInfo[B.type] :: TypeInfo[A.type] :: TypeInfo[C.type] :: HNil
val hlist: HType = Ainfo() :: Binfo() :: Ainfo2() :: Cinfo() :: HNil
You can use filter on the HList directly:
hlist.filter[TypeInfo[A.type]] // Ainfo() :: Ainfo2() :: HNil
If you want to avoid explicitly specifying TypeInfo in the filter call you can modify your filter function (but now you're required to give the HList type -- this can be worked around using a proxy class):
def filterByEnumType[T <: EnumType, L <: HList](
list: L
)(implicit filter: Filter[L, TypeInfo[T]]): filter.Out = {
filter.apply(list)
}
filterByEnumType[A.type, HType](hlist) // Ainfo() :: Ainfo2() :: HNil
I'm trying to use shapeless's hlist to construct introspectable URL templates, but I'm having trouble with traversing my HList. The following doesn't compile:
import shapeless.{::, HList, HNil}
import shapeless.LUBConstraint._
import shapeless.ops.hlist.ToTraversable._
import scala.util.Try
import shapeless.ops.hlist._
object Path {
def /(s: String) = Path(PathLiteral(s) :: HNil)
def param[T](name: String) = PathParameter(name)
}
sealed trait PathSegment[+T]
case class PathLiteral(value: String) extends PathSegment[String]
case class PathParameter[+T](name: String) extends PathSegment[T]
case class Path[L <: HList : <<:[PathSegment[_]]#λ](segments: L)
(implicit ev: ToList[L, PathSegment[_]])
{
def /(literal: String) = Path(PathLiteral(literal) :: segments)
def /[T](param: PathParameter[T]) = Path(param :: segments)
override def toString: String = s"Path(${segments.toList.reverse})"
}
object Test extends App {
import Path.param
val myPath = Path / "v1" / "pets" / param[String]("name") / "pictures"
println(myPath)
}
It seems to me like the ToTraversable._ import covers the HNil case and the case of having the tail of an HList and a new head with the same Least Upper Bound. Obviously I'm either missing an import or misunderstanding everything.
I'm not sure if caching the evidence in the class as an implicit parameter is kosher; I'm doing it because
I don't want hlist details to leak into the external API
I need it for a nice toString
The reason this doesn't work is the implicit which can prove:
ToList[PathLiteral :: L, PathSegment[_]] given
ToList[L, PathSegment[L]]
L <: HList
is this implicit def's type signature from hlists.scala:
implicit def hlistToTraversable[H1, H2, T <: HList, LubT, Lub0, M[_]]
(implicit
tttvs : Aux[H2 :: T, M, LubT],
u : Lub[H1, LubT, Lub0],
cbf : CanBuildFrom[M[Lub0], Lub0, M[Lub0]]) : Aux[H1 :: H2 :: T, M, Lub0]
actually needs to know the type of the head of the list as well as the new item (H1 and H2), so it can't be constructed from what's available inside my class.
Some workarounds:
build List manually alongside the HList
case class Path[L <: HList](segments: L, segmentsList: List[PathSegment[_]]) {
def /(literal: String) = Path(PathLiteral(literal) :: segments, PathLiteral(literal) :: segmentsList)
def /[T](param: PathParameter[T]) = Path(param :: segments, param :: segmentsList)
override def toString: String = s"Path(${segmentsList.reverse})"
}
push implicit parameters out to Path's methods
def printPath[L <: HList](path: Path[L])
(implicit ev: ToList[L, PathSegment[_]]) =
path.segments.toList.reverse.collect {
case PathLiteral(value) => URLEncoder.encode(value, "UTF-8")
case PathParameter(name) => s"<$name>"
}.mkString("/")
printPath(Path / "v1" / "pets" / param[String]("name") / "pictures")
// v1/pets/<name>/pictures
I have a problem with having a Function1 in my trait and make things work correctly, let's say I have the following trait
trait Foo[+X, +O, Z<:X]{
def bar: Z => O
def args:Option[Z]
}
case class A1(args:Option[String] = None) extends Foo[String,String,String]{
def bar = (x:String) => x+"..."
}
case class A2(args:Option[Int] = None) extends Foo[Int,String,Int]{
def bar = (x:Int) => x.toString
}
val mylist = List(A1(),A2())
The reason that I have type Z is for type signature of Function1 which is [T1-,R+]. I want mylist has type of Foo[Any,Any,Any] now it is Foo[Any,Any,_ >: Int with String]. So basically I want Z to be covariant too which is impossible. How should I define my trait to solve these problems?
Given an arbitrary case class Cc( a: String, b: Int ) and a type class trait Tc[T], is there a way to materialize a simple HList (or preferably a labelled record) that contains a type class instance for each parameter of Cc?
def resolveTypeClasses[C] = ???
val resolved: Tc[String] :: Tc[Int] :: HNil = resolveTypeClasses[Cc]
It's hard to make single type parameter function because of need to resolve intermediate Generic.
So from my noob point of view it should be two parameter def, or sequence of two defs, each taking single type parameter. Second variant could be implemented like:
import shapeless._
trait Tc[T]
case class Cc(a: String, b: Int)
trait ResolveTC[L] {
type Out <: HList
val out: Out
}
implicit object ResolveHNilTC extends ResolveTC[HNil] {
type Out = HNil
val out: HNil = HNil
}
implicit def resolveHListTC[X, XS <: HList](implicit tc: Tc[X], rest: ResolveTC[XS]) =
new ResolveTC[X :: XS] {
type Out = Tc[X] :: rest.Out
val out = tc :: rest.out
}
class TCResolver[C] {
def get[L <: HList](implicit gen: Generic.Aux[C, L], resolve: ResolveTC[L]): resolve.Out = resolve.out
}
def resolveTypeClasses[C] = new TCResolver[C]
implicit case object StringInst extends Tc[String]
implicit case object IntInst extends Tc[Int]
with that implementation
val resolved = resolveTypeClasses[Cc].get
will produce the
StringInst :: IntInst :: HNil
I am currently implementing a library to serialize and deserialize to and from XML-RPC messages. It's almost done but now I am trying to remove the boilerplate of my current asProduct method using Shapeless. My current code:
trait Serializer[T] {
def serialize(value: T): NodeSeq
}
trait Deserializer[T] {
type Deserialized[T] = Validation[AnyErrors, T]
type AnyErrors = NonEmptyList[AnyError]
def deserialize(from: NodeSeq): Deserialized[T]
}
trait Datatype[T] extends Serializer[T] with Deserializer[T]
// Example of asProduct, there are 20 more methods like this, from arity 1 to 22
def asProduct2[S, T1: Datatype, T2: Datatype](apply: (T1, T2) => S)(unapply: S => Product2[T1, T2]) = new Datatype[S] {
override def serialize(value: S): NodeSeq = {
val params = unapply(value)
val b = toXmlrpc(params._1) ++ toXmlrpc(params._2)
b.theSeq
}
// Using scalaz
override def deserialize(from: NodeSeq): Deserialized[S] = (
fromXmlrpc[T1](from(0)) |#| fromXmlrpc[T2](from(1))
) {apply}
}
My goal is to allow the user of my library to serialize/deserialize case classes without force him to write boilerplate code. Currently, you have to declare the case class and an implicit val using the aforementioned asProduct method to have a Datatype instance in context. This implicit is used in the following code:
def toXmlrpc[T](datatype: T)(implicit serializer: Serializer[T]): NodeSeq =
serializer.serialize(datatype)
def fromXmlrpc[T](value: NodeSeq)(implicit deserializer: Deserializer[T]): Deserialized[T] =
deserializer.deserialize(value)
This is the classic strategy of serializing and deserializing using type classes.
At this moment, I have grasped how to convert from case classes to HList via Generic or LabelledGeneric. The problem is once I have this conversion done how I can call the methods fromXmlrpc and toXmlrpc as in the asProduct2 example. I don't have any information about the types of the attributes in the case class and, therefore, the compiler cannot find any implicit that satisfy fromXmlrpc and toXmlrpc. I need a way to constrain that all the elements of a HList have an implicit Datatype in context.
As I am a beginner with Shapeless, I would like to know what's the best way of getting this functionality. I have some insights but I definitely have no idea of how to get it done using Shapeless. The ideal would be to have a way to get the type from a given attribute of the case class and pass this type explicitly to fromXmlrpc and toXmlrpc. I imagine that this is not how it can be done.
First, you need to write generic serializers for HList. That is, you need to specify how to serialize H :: T and HNil:
implicit def hconsDatatype[H, T <: HList](implicit hd: Datatype[H],
td: Datatype[T]): Datatype[H :: T] =
new Datatype[H :: T] {
override def serialize(value: H :: T): NodeSeq = value match {
case h :: t =>
val sh = hd.serialize(h)
val st = td.serialize(t)
(sh ++ st).theSeq
}
override def deserialize(from: NodeSeq): Deserialized[H :: T] =
(hd.deserialize(from.head) |#| td.deserialize(from.tail)) {
(h, t) => h :: t
}
}
implicit val hnilDatatype: Datatype[HNil] =
new Datatype[HNil] {
override def serialize(value: HNil): NodeSeq = NodeSeq()
override def deserialize(from: NodeSeq): Deserialized[HNil] =
Success(HNil)
}
Then you can define a generic serializer for any type which can be deconstructed via Generic:
implicit def genericDatatype[T, R](implicit gen: Generic.Aux[T, R],
rd: Lazy[Datatype[R]]): Datatype[T] =
new Datatype[T] {
override def serialize(value: T): NodeSeq =
rd.value.serialize(gen.to(value))
override def deserialize(from: NodeSeq): Deserialized[T] =
rd.value.deserialize(from).map(rd.from)
}
Note that I had to use Lazy because otherwise this code would break the implicit resolution process if you have nested case classes. If you get "diverging implicit expansion" errors, you could try adding Lazy to implicit parameters in hconsDatatype and hnilDatatype as well.
This works because Generic.Aux[T, R] links the arbitrary product-like type T and a HList type R. For example, for this case class
case class A(x: Int, y: String)
shapeless will generate a Generic instance of type
Generic.Aux[A, Int :: String :: HNil]
Consequently, you can delegate the serialization to recursively defined Datatypes for HList, converting the data to HList with Generic first. Deserialization works similarly but in reverse - first the serialized form is read to HList and then this HList is converted to the actual data with Generic.
It is possible that I made several mistakes in the usage of NodeSeq API above but I guess it conveys the general idea.
If you want to use LabelledGeneric, the code would become slightly more complex, and even more so if you want to handle sealed trait hierarchies which are represented with Coproducts.
I'm using shapeless to provide generic serialization mechanism in my library, picopickle. I'm not aware of any other library which does this with shapeless. You can try and find some examples how shapeless could be used in this library, but the code there is somewhat complex. There is also an example among shapeless examples, namely S-expressions.
Vladimir's answer is great and should be the accepted one, but it's also possible to do this a little more nicely with Shapeless's TypeClass machinery. Given the following setup:
import scala.xml.NodeSeq
import scalaz._, Scalaz._
trait Serializer[T] {
def serialize(value: T): NodeSeq
}
trait Deserializer[T] {
type Deserialized[T] = Validation[AnyErrors, T]
type AnyError = Throwable
type AnyErrors = NonEmptyList[AnyError]
def deserialize(from: NodeSeq): Deserialized[T]
}
trait Datatype[T] extends Serializer[T] with Deserializer[T]
We can write this:
import shapeless._
object Datatype extends ProductTypeClassCompanion[Datatype] {
object typeClass extends ProductTypeClass[Datatype] {
def emptyProduct: Datatype[HNil] = new Datatype[HNil] {
def serialize(value: HNil): NodeSeq = Nil
def deserialize(from: NodeSeq): Deserialized[HNil] = HNil.successNel
}
def product[H, T <: HList](
dh: Datatype[H],
dt: Datatype[T]
): Datatype[H :: T] = new Datatype[H :: T] {
def serialize(value: H :: T): NodeSeq =
dh.serialize(value.head) ++ dt.serialize(value.tail)
def deserialize(from: NodeSeq): Deserialized[H :: T] =
(dh.deserialize(from.head) |#| dt.deserialize(from.tail))(_ :: _)
}
def project[F, G](
instance: => Datatype[G],
to: F => G,
from: G => F
): Datatype[F] = new Datatype[F] {
def serialize(value: F): NodeSeq = instance.serialize(to(value))
def deserialize(nodes: NodeSeq): Deserialized[F] =
instance.deserialize(nodes).map(from)
}
}
}
Be sure to define these all together so they'll be properly companioned.
Then if we have a case class:
case class Foo(bar: String, baz: String)
And instances for the types of the case class members (in this case just String):
implicit object DatatypeString extends Datatype[String] {
def serialize(value: String) = <s>{value}</s>
def deserialize(from: NodeSeq) = from match {
case <s>{value}</s> => value.text.successNel
case _ => new RuntimeException("Bad string XML").failureNel
}
}
We automatically get a derived instance for Foo:
scala> case class Foo(bar: String, baz: String)
defined class Foo
scala> val fooDatatype = implicitly[Datatype[Foo]]
fooDatatype: Datatype[Foo] = Datatype$typeClass$$anon$3#2e84026b
scala> val xml = fooDatatype.serialize(Foo("AAA", "zzz"))
xml: scala.xml.NodeSeq = NodeSeq(<s>AAA</s>, <s>zzz</s>)
scala> fooDatatype.deserialize(xml)
res1: fooDatatype.Deserialized[Foo] = Success(Foo(AAA,zzz))
This works about the same as Vladimir's solution, but it lets Shapeless abstract some of the boring boilerplate of type class instance derivation so you don't have to get your hands dirty with Generic.