This question is similar to this one, except that both case class instances need to be accessed with references to their base trait. The reason for this is that more than one case class will be extending the trait, and the exact type won't be known until runtime:
sealed trait baseData {
def weight: Int
def priority: Int
}
sealed trait moreData {
def weight: Int
def priority: Int
def t: String
def id: String
}
case class data1(override val weight: Int, override val priority: Int) extends baseData
case class moreData1 (override val weight:Int, override val priority: Int, override val t: String, override val id: String)extends moreData
val from: baseData = data1(1,2)
val to: moreData = moreData1(3,4,"a","b")
How to write a function with the following signature that copies from into to?
def copyOver[A <:baseData, B <:moreData](from: A, to: B)
I'm sure this is doable with Shapeless, but I'm having trouble since I'm pretty new to it.
Related
I currently have something like this:
case class Bear(a: String, b: String) {
val can: Can[T] = ??
def drink[T](str: String) = can.open(str)
}
I need to modify this to be used for only 4 types A,B,C and D. For example given an instance of Bear we should only be able to call bearinstance.drink[A]("abc"), bearinstance.drink[B]("abc"), bearinstance.drink[C]("abc") and bearinstance.drink[D]("abc"). Any other type should not be allowed.
Now the question is how do I rewrite this method for specific types?
Another issue is with the can, assuming I manage to rewrite drink to be used with only types 'A', 'B', 'C' and 'D', I will have to create can for all the four types as member variables. How do I make generic method to dynamically select the can based on the type? One option is to implicitly declare can outside the class, but it requires class parameters to be declared.
Any leads will be appreciated.
The fact that you need to do this means you really should refactor your code.
But anyways...
Try using implicit parameters:
case class Bear(a: String, b: String) {
val can: Can[T] = ???
def drink[T](str: String)(implicit ev: CanDrink[T]) = can.open(str)
}
Then make a trait CanDrink with implicit instances:
trait CanDrink[T]
implicit object ACanDrink extends CanDrink[A]
implicit object BCanDrink extends CanDrink[B]
//And so on
And now you can call it like this:
bearinstance.drink[A]("abc")
//Gets implicit object ACanDrink
bearinstance.drink[X]("abc")
//Doesn't work because no implicit parameter given of type CanDrink[X]
In Dotty, you could try changing the definition of drink using union types, as suggested by Dmytro Mitin:
def drink(x: A | B | C | D)(str: String) = ???
def drink[T](str: String)(using T <:< (A | B | C | D)) = ???
If you need it to be determined dynamically, use ClassTag.
def drink[T](str: String)(implicit ev: ClassTag[T]) = ev match {
case classOf[A] => ???
...
}
If you're actually overriding generic method you must implement it for all possible types of type parameters (otherwise you violate the contract of class):
trait BearLike {
def drink[T](str: String)
}
case class Bear(a: String, b: String) extends BearLike {
override def drink[T](str: String) = ??? // for all T
}
or
case class Bear(a: String, b: String) {
def drinkABCD[T](str: String)(implicit can: Can[T]) = can.open(str) // only for A, ..., D
}
or
case class Bear(a: String, b: String) extends BearLike {
override def drink[T](str: String): Unit = sys.error("Bear is not actually a BearLike")
def drinkABCD[T](str: String)(implicit can: Can[T]) = can.open(str) // only for A, ..., D
}
provided there are Can[A], ..., Can[D]
trait Can[T] {
def open(str: String)
}
object Can {
implicit val a: Can[A] = ???
implicit val b: Can[B] = ???
implicit val c: Can[C] = ???
implicit val d: Can[D] = ???
}
If you can modify the contract then you can add this restriction (that the method works only for A, ..., D) to the contract
trait BearLike {
def drink[T](str: String)(implicit can: Can[T])
}
case class Bear(a: String, b: String) extends BearLike {
override def drink[T](str: String)(implicit can: Can[T]) = can.open(str)
}
Sometimes it's not easy to combine FP (type classes) with OOP (inheritance). Normally if you start to work with type classes (Can) you should prefer type classes further on
trait BearLike[B] {
def drink(str: String)
}
case class Bear[T](a: String, b: String)
object Bear {
implicit def bearIsBearLike[T](implicit can: Can[T]): BearLike[Bear[T]] = new BearLike[Bear[T]] {
override def drink(str: String): Unit = can.open(str)
}
}
How to define "type disjunction" (union types)?
Right now I'm writing a lot of code like the following example, but the apply methods are almost equal, the only thing that changes is the resulting class. Would it be possible to use the same apply method to instantiate different implementations of a trait?
trait Base {
def name: Option[String]
def value: Int
}
case class AnImplementatation(override val name: Option[String]) extends Base {
override def value: Int = name.map(_.length).getOrElse(0)
}
object AnImplementatation {
def apply(name: String): AnImplementatation = AnImplementatation(Some(name))
}
case class AnotherImplementatation(override val name: Option[String]) extends Base {
override def value: Int = name.map(_.length).getOrElse(0) * 2
}
object AnotherImplementatation {
def apply(name: String): AnotherImplementatation = AnotherImplementatation(Some(name))
}
Ok so I don't know what's bugging in this code:
import scala.reflect.runtime.universe._
trait Key extends Product
case class SomeKey(a: Int, b: String) extends Key
case class SomeOtherKey(a: Int, b: String, c:Boolean) extends Key
trait MyTrait[T <: Key] {
def someField: Int
def someFunc(implicit tTypeTag: TypeTag[T]): Map[T, Int] = {
typeOf(tTypeTag) match {
case t if t =:= typeOf[SomeKey] => Map(SomeKey(1,"2") -> 1)
case t if t =:= typeOf[SomeOtherKey] => Map(SomeOtherKey(1,"2",true) -> 2)
}
}
}
I want (the example has been oversimplified) to be able to return a Map[SomeKey, Int] if someFunc is called from a case class extending MyTrait[SomeKey]. And return a Map[SomeOtherKey, Int] from a MyTrait[SomeOtherKey]
case class MyClass(val s: Int) extends MyTrait[SomeKey] {
override def someField = s
}
Here a new instance of MyClass should return a Map[SomeKey, Int] when calling someFunc.
But it does not even compile, compiler complaining for each line of the pattern match:
type mismatch;
found : (Playground.this.SomeKey, Int)
required: (T, Int)
or
type mismatch;
found : (Playground.this.SomeOtherKey, Int)
required: (T, Int)
Here's a solution using type classes and implicits.
trait Key extends Product
case class SomeKey(a: Int, b: String) extends Key
case class SomeOtherKey(a: Int, b: String, c:Boolean) extends Key
trait TypeClass[T] {
def someFunc: Map[T, Int]
}
object TypeClass {
implicit def forSomeKey: TypeClass[SomeKey] = new TypeClass[SomeKey] {
override def someFunc: Map[SomeKey, Int] = Map(SomeKey(1, "2") -> 1)
}
implicit def forSomeOtherKey: TypeClass[SomeOtherKey] = new TypeClass[SomeOtherKey] {
override def someFunc: Map[SomeOtherKey, Int] = Map(SomeOtherKey(1, "2", true) -> 1)
}
}
trait MyTrait[T <: Key] {
def someField: Int
def someFunc(implicit tc: TypeClass[T]): Map[T, Int] = tc.someFunc
}
TypeTag will carry over type information to runtime however return-type of a method is compile-time construct, hence the compiler error. Instead consider typeclass solution via extension method (yet again hijacked from Luis' suggestion)
sealed trait Key
final case class SomeKey(a: Int, b: String) extends Key
final case class SomeOtherKey(a: Int, b: String, c: Boolean) extends Key
trait MyTrait[T <: Key]
trait KeyFactory[T <: Key] {
def someFunc(): Map[T, Int]
}
object KeyFactory {
def someFunc[T <: Key](implicit ev: KeyFactory[T]) = ev.someFunc
implicit val someKeyFoo: KeyFactory[SomeKey] = () => Map(SomeKey(1,"2") -> 1)
implicit val someOtherKey: KeyFactory[SomeOtherKey] = () => Map(SomeOtherKey(1,"2", true) -> 2)
}
implicit final class MyTraitKeyFactory[T <: Key : KeyFactory](private val v: MyTrait[T]) {
def someFunc(): Map[T, Int] = implicitly[KeyFactory[T]].someFunc()
}
case class MyClass(s: Int) extends MyTrait[SomeKey]
case class MyOtherClass(s: Int) extends MyTrait[SomeOtherKey]
MyOtherClass(42).someFunc()
MyClass(11).someFunc()
which outputs
res0: Map[SomeOtherKey,Int] = Map(SomeOtherKey(1,2,true) -> 2)
res1: Map[SomeKey,Int] = Map(SomeKey(1,2) -> 1)
I have the following setup, where I want to copy an instance of baseData into that of moreData:
sealed trait baseData {
def weight: Int
def priority: Int
}
sealed trait moreData {
def weight: Int
def priority: Int
def t: String
def id: String
}
case class data1(override val weight: Int, override val priority: Int) extends baseData
case class moreData1 (override val weight:Int, override val priority: Int, override val t: String, override val id: String)extends moreData
So copying myData into otherData below:
val myData = data1(1,1)
val otherData = moreData1 (2,2,"C","abcd")
would yield: moreData1(1,1,"C","abcd").
To do this, I want to use a function with the following signature, because I will have more than one case class extending both baseData and moreData:
def copyOver[A <:baseData, B <:moreData](from: A, to: B) = {}
I'm sure you can do this with Shapeless, but haven't figured out how. There are examples (here) on copying case classes extending a same trait, and others (here) mapping values between different case classes via generic representation. But I haven't figured out how to use LabelledGeneric with the trait-bounded arguments passed into copyOver. I also don't want to have to hardcode the extra fields in otherData that aren't present in myData.
I'm looking for a completely generic implementation. Any ideas?
You should be able to use the UpdateRepr type class from your first shapeless example.
You could define copyOver using UpdateRepr as follows :
import shapeless._
// baseData, moreData, data1, moreData1
// UpdateRepr ...
def copyOver[A <: baseData, B <: moreData, R <: HList](
from: A,
to: B
)(implicit
lgen: LabelledGeneric.Aux[A, R],
update: UpdateRepr[B, R]
): B = update(to, lgen.to(from))
Which you could use as follows :
val myData = data1(1,1)
val otherData = moreData1(2,2,"C","abcd")
copyOver(myData, otherData)
// moreData1 = moreData1(1,1,C,abcd)
Note that it is possible you run into problems with SI-7046, because of the sealed trait (Coproduct) type class derivation for UpdateRepr, which you could notice in the REPL or when splitting UpdateRepr and the sealed traits over multiple files.
I have two classes with some data members like this:
class MyInfo {
private val myId: String
private val time: DateTime
private val solution: Long
private val banner: Int
}
Class HisInfo {
private val id: String
private val solution: Long
private val banner: Int
}
As you can see these two classes share two members, and in my real project they share more. I need to save them into hbase and designed a class like this:
sealed trait Element[T] {
def serialize(value: T)(implicit val helper: Helper[T]): Array[Byte]
def deserialize(bytes: Array[Byte])(implicit val helper: Helper[T]): T
}
case object LongElement extends Element[Long] {...}
case object IntElement extends Element[Int] {...}
class Info {
protected val data: Map[Element[_], Any] = new mutable.Map[Element[_], Any]()
}
class MyInfo extends Info {
val elements = List(LongElement, IntLement)
def saveToHBase = {
elements.foreach { e =>
val v = e.serialize(data(e))
// do sth with v
}
}
In fact I have defined implementations of Helper[Int] and Helper[Long], but the compiler complains that it can not find implicit value for parameter Helper[_1]. Can someone help me to design these classes?
In fact I have defined implementations of Helper[Int] and Helper[Long], but the compiler complains that it can not find implicit value for parameter Helper[_1].
Well, consider what would happen if elements included an Element[String] (or some other type for which you have no implicit Helper). Given the type of elements, the compiler can't know it doesn't.
I think that if you need a Helper for all methods of Element anyway, you should just make it a part of the type:
sealed trait Element[T] {
val helper: Helper[T] // or def if you want to put it in the companion object
def serialize(value: T): Array[Byte]
def deserialize(bytes: Array[Byte]): T
}
or
sealed abstract class Element[T](implicit val helper: Helper[T]) {
def serialize(value: T): Array[Byte]
def deserialize(bytes: Array[Byte]): T
}
At least in most situations.
It definitely looks like work for shapeless
Cool thing about shapeless, it can convert your class to list like structure named HList.
Scala ordinary collections like List should drop information about elements to bring them to common type.
HList could save type of each elements, while providing List-like functionality
Lets define your type with their common fields in separate type
import org.joda.time.DateTime
class Common(
val solution: Long,
val banner: Int
)
class MyInfo(
myId: String,
time: DateTime,
solution: Long,
banner: Int
) extends Common(solution, banner)
class HistInfo(
id: String,
solution: Long,
banner: Int
) extends Common(solution, banner)
Now lets define something looks like serialization:
trait Helper[T] extends (T => Array[Byte])
implicit object longHelper extends Helper[Long] {
def apply(x: Long) = 0 to 7 map (i => (x >> (i * 8)).toByte) toArray
}
implicit object intHelper extends Helper[Int] {
def apply(x: Int) = 0 to 3 map (i => (x >> (i * 8)).toByte) toArray
}
Now something interesting. We'll create special object that could convert your Common type to special HList which contains every value preserving it's type information and statically-saved string with field name:
import shapeless._
val lgen = LabelledGeneric[Common]
Next we define special function-like thing to map over such HList. It would find known implicit Helper and tie its result with corresponding field name:
import shapeless.labelled.FieldType
object serialized extends Poly1 {
implicit def serialize[K <: Symbol, T]
(implicit helper: Helper[T], key: Witness.Aux[K])
= at[FieldType[K, T]](field => key.value.name -> helper(field))
}
Now we define some user of this function:
def extractMap(x: Common): Map[String, Seq[Byte]] =
lgen.to(histInfo).map(serialized).toList.toMap.mapValues(_.toSeq)
You could verify you function is working:
val histInfo = new HistInfo("123", 12, 3)
println(extractMap(histInfo))
will print
Map(solution -> WrappedArray(12, 0, 0, 0, 0, 0, 0, 0), banner ->
WrappedArray(3, 0, 0, 0))