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.
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)?
I'm running into an issue where I am working with several Traits that use dependent typing, but when I try to combine the Traits in my business logic, I get a compilation error.
import java.util.UUID
object TestDependentTypes extends App{
val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))
RealConfLoader(7).extractData(myConf.settings)
}
trait Data
case class RealData(anInt: Int, aDouble: Double) extends Data
trait MySettings
case class RealSettings(data: RealData) extends MySettings
trait Conf {
type T <: MySettings
def id: UUID
def settings: T
}
case class RealConf(id: UUID, settings: RealSettings) extends Conf {
type T = RealSettings
}
trait ConfLoader{
type T <: MySettings
type U <: Data
def extractData(settings: T): U
}
case class RealConfLoader(someInfo: Int) extends ConfLoader {
type T = RealSettings
type U = RealData
override def extractData(settings: RealSettings): RealData = settings.data
}
The code in processor will not compile because extractData expects input of type ConfLoader.T, but conf.settings is of type Conf.T. Those are different types.
However, I have specified that both must be subclasses of MySettings, so it should be the case I can use one where the other is desired. I understand Scala does not compile the code, but is there some workaround so that I can pass conf.settings to confLoader.extractData?
===
I want to report that for the code I wrote above, there is a way to write it that would decrease my usage of dependent types. I noticed today while experimenting with Traits that Scala supports subclassing on defs and vals on classes that implement the Trait. So I only need to create a dependent type for the argument for extractData, and not the output.
import java.util.UUID
object TestDependentTypes extends App{
val myConf = RealConf(UUID.randomUUID(), RealSettings(RealData(5, 25.0)))
RealConfLoader(7).extractData(myConf.settings)
def processor(confLoader: ConfLoader, conf: Conf) = confLoader.extractData(conf.settings.asInstanceOf[confLoader.T])
}
trait Data
case class RealData(anInt: Int, aDouble: Double) extends Data
trait MySettings
case class RealSettings(data: RealData) extends MySettings
trait Conf {
def id: UUID
def settings: MySettings
}
case class RealConf(id: UUID, settings: RealSettings) extends Conf
trait ConfLoader{
type T <: MySettings
def extractData(settings: T): Data
}
case class RealConfLoader(someInfo: Int) extends ConfLoader {
type T = RealSettings
override def extractData(settings: RealSettings): RealData = settings.data
}
The above code does the same thing and reduces dependence on dependent types. I have only removed processor from the code. For the implementation of processor, refer to any of the solutions below.
The code in processor will not compile because extractData expects input of type ConfLoader.T, but conf.settings is of type Conf.T. Those are different types.
In the method processor you should specify that these types are the same.
Use type refinements (1, 2) for that: either
def processor[_T](confLoader: ConfLoader { type T = _T }, conf: Conf { type T = _T }) =
confLoader.extractData(conf.settings)
or
def processor(confLoader: ConfLoader)(conf: Conf { type T = confLoader.T }) =
confLoader.extractData(conf.settings)
or
def processor(conf: Conf)(confLoader: ConfLoader { type T = conf.T }) =
confLoader.extractData(conf.settings)
IMHO if you don't need any of the capabilities provided by dependent types, you should just use plain type parameters.
Thus:
trait Conf[S <: MySettings] {
def id: UUID
def settings: S
}
final case class RealConf(id: UUID, settings: RealSettings) extends Conf[RealSettings]
trait ConfLoader[S <: MySettings, D <: Data] {
def extractData(settings: S): D
}
final case class RealConfLoader(someInfo: Int) extends ConfLoader[RealSettings, RealData] {
override def extractData(settings: RealSettings): RealData =
settings.data
}
def processor[S <: MySettings, D <: Data](loader: ConfLoader[S, D])(conf: Conf[S]): D =
loader.extractData(conf.settings)
But, if you really require them to be type members, you may ensure both are the same.
def processor(loader: ConfLoader)(conf: Conf)
(implicit ev: conf.S <:< loader.S): loader.D =
loader.extractData(conf.settings)
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.
Given this case class case class Location(id: BigInt, lat: Double, lng: Double)
and a list of strings List("87222", "42.9912987", "-93.9557953")
I would like to do something like Location.fromString(listOfString) in a way Location instance is created with string types converted into appropriate types.
Only way I can think of is define fromString method in each case class but I'm looking for a more generic solution each class can inherit. For example, I would have case class Location(...) extends Record, where Record implements fromString that does conversion based on argument types defined in Location class.
Normally for best performance in such you need to create some macro.
Luckily there is beautiful shapeless library with which you could create such generic reader with near-macro performance in no time using it's generic case class representation
First define typeclass and some instances for reading your fields:
trait Read[T] {
def fromString(s: String): T
}
implicit object readBigInt extends Read[BigInt] {
def fromString(s: String): BigInt = BigInt(s)
}
implicit object readDouble extends Read[Double] {
def fromString(s: String): Double = s.toDouble
}
Next define your main typeclass:
trait ReadSeq[T] {
def fromStrings(ss: Seq[String]): T
}
Now it's shapeless time (thinking about DalĂ?) . We create reader for powerful Heterogenous List, which is almost like List but with of statically known length and element types.
It's no harder than to match simple List. Just define cases for empty list and cons:
import shapeless._
implicit object readHNil extends ReadSeq[HNil] {
def fromStrings(ss: Seq[String]) = HNil
}
implicit def readHList[X, XS <: HList](implicit head: Read[X], tail: ReadSeq[XS]) =
new ReadSeq[X :: XS] {
def fromStrings(ss: Seq[String]) = ss match {
case s +: rest => (head fromString s) :: (tail fromStrings rest)
}
}
Now we can use out-of-the-box macro mapping of HLists and case classes via Generic:
implicit def readCase[C, L <: HList](implicit gen: Generic.Aux[C, L], read: ReadSeq[L]) =
new ReadSeq[C] {
def fromStrings(ss: Seq[String]) = gen.from(read.fromStrings(ss))
}
Finally we could build our typeclass user:
def fromStringSeq[T](ss: Seq[String])(implicit read: ReadSeq[T]) = read.fromStrings(ss)
from this point having
case class Location(id: BigInt, lat: Double, lng: Double)
val repr = List("87222", "42.9912987", "-93.9557953")
you can ensure that
fromStringSeq[Location](repr) == Location(BigInt("87222"), 42.9912987, 93.9557953)
Scala allows to define update such as
def update(index: Int, value: String) { ... }
and then call it like
foo(i) = "Text"
Is there a trait that encapsulates that? Something like
trait Update1[+A,+B] {
def update(i: A, v: B)
}
(Of course I could define such a trait myself, but it would only work for instances that I mix with it, not with other ones constructed beyond my influence.)
The reason why you can't define such a trait is that you are using covariant type parameters in a place where they are not allowed. The following traits compile fine in Scala 2.10:
trait Update1[-A, -B] {
def update(i: A, v: B) : Unit
}
trait MyFunction1[-A, +B] {
def apply(a:A): B
}
trait Mutable[-A, B] extends Update1[A,B] with MyFunction1[A,B]
Notice that in order to have a mutable trait you have to fix the B parameter, so it allows neither covariance nor contravariance. If you take a look at the mutable collections in the Scala API you can see that in fact this is how they are declared.
In addition, nothing prevents you to mix a trait in an object instead of in a class to make the compiler happy, if you know that class already implements the methods defined in the trait. For example, you can have the following:
class SingleStringCollection(v: String) extends MyFunction1[Int, String] {
private var someString: String = v
def apply(a: Int): String = someString
def update(i: Int, v: String): Unit = {
someString = v
}
override def toString = someString
}
val test1: Update1[Int, String] = new SingleStringCollection("hi") // this would fail
val test2: Update1[Int, String] = new SingleStringCollection("hi") with Update1[Int, String] // this would work
Or you could also use structural typing if you just want to require that your val or parameter implements a list of known methods:
type UpdatableStructuralType = {
def update(i: Int, v: String) : Unit
}
val test3: UpdatableStructuralType = new SingleStringCollection("hi") // this would work
test3(0) = "great" // And of course this would also work
So you have several alternatives if you want to accept parameters conforming to some trait or requiring some methods to be implemented.