I am just dipping my toes into generics and am wondering if there is a better way to achieve the following:
I have a sealed trait that has an abstract name and an overridden equals(). I want the overridden equals to match on both type and name. Below is what I have.
sealed trait NamedCampaign[A <: NamedCampaign] {
def name: String
override def equals(obj: Any): Boolean = obj match {
case x: A => x.name == this.name
case _ => false
}
}
case class AdCampaign(name: String, objective: String, status: String, buyingType: String) extends NamedCampaign[AdCampaign]
case class AdSet(name: String, status: String, dailyBudget: Int, lifetimeBudget: Int, startTime: Int, endTime: Int, campaign: String) extends NamedCampaign[AdSet]
In layman's terms, I want two objects to be considered equal if they are the same class and have the same name. Is there a better/faster/more idiomatic way of doing this?
What you have can't work because of erasure. The type A isn't known at runtime.
Adapted from this related answer:
sealed trait NamedCampaign[A <: NamedCampaign] {
implicit def classTagA: ClassTag[A]
def name: String
override def equals(obj: Any): Boolean = obj match {
case classTagA(x) => x.name == this.name
case _ => false
}
}
case class AdCampaign(name: String, objective: String, status: String,
buyingType: String)(implicit val classTagA: ClassTag[AdCampaign])
extends NamedCampaign[AdCampaign]
case class AdSet(name: String, status: String, dailyBudget: Int,
lifetimeBudget: Int, startTime: Int, endTime: Int, campaign: String)
(implicit val classTagA: ClassTag[AdSet]) extends NamedCampaign[AdSet]
A better way to write this is with a canEqual method.
sealed trait NamedCampaign {
def name: String
def canEqual(that: Any): Boolean
override def equals(other: Any): Boolean = other match {
case that: NamedCampaign => (that canEqual this) &&
(this.name == that.name)
case _ => false
}
}
case class AdCampaign(name: String, objective: String, status: String,
buyingType: String) extends NamedCampaign {
override def canEqual(that: Any) = that.isInstanceOf[AdCampaign]
}
case class AdSet(name: String, status: String, dailyBudget: Int,
lifetimeBudget: Int, startTime: Int, endTime: Int, campaign: String)
extends NamedCampaign {
override def canEqual(that: Any) = that.isInstanceOf[AdSet]
}
My two cents: I don't think it's ever appropriate to override equals on a case class. You'll regret it the moment you ever want to compare all of the fields (which you're likely to want to do in, say, a unit test).
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)?
In the project that I am working on, there is some code that is essentially as follows:
sealed trait Character {
def tags: Seq[String]
def life: Int
// other defs
}
object Character {
def addTag[T <: Character](character: T, tag: String): T = {
val newTags = character.tags :+ tag
// character.copy(tags = newTags) // this doesn't compile
character match {
case c: Person => c.copy(tags = newTags).asInstanceOf[T]
case c: Beast => c.copy(tags = newTags).asInstanceOf[T]
// ten more cases to match each subclass
......
case _ => character
}
}
}
case class Person(title: String,
firstName: String,
lastName: String,
tags: Seq[String],
life: Int,
weapon: String
) extends Character
case class Beast(name: String,
tags: Seq[String],
life: Int,
weight: Int
) extends Character
// ten other case classes that extends Character
......
The code works, but the addTag method doesn't look very pretty for two reasons: first, it uses asInstanceOf; second, it has many lines of case c: ...... each of which are almost the same.
Is there a way to make the code better?
Since the copy method is specific to each case class (takes different parameters) it can't be used from a superclass. What you could do is:
sealed trait Character {
def tags: Seq[String]
def life: Int
// other defs
}
trait Taggable[T <: Character] {
def addTags(t: T, newTags: Seq[String]): T
}
object Character {
def addTag[T <: Character: Taggable](character: T, tag: String): T = {
val newTags = character.tags :+ tag
implicitly[Taggable[T]].addTags(character, newTags)
}
}
case class Person(title: String,
firstName: String,
lastName: String,
tags: Seq[String],
life: Int,
weapon: String
) extends Character
object Person {
implicit val taggable: Taggable[Person] = new Taggable[Person] {
override def addTags(t: Person, newTags: Seq[String]): Person = t.copy(tags = newTags)
}
}
case class Beast(name: String,
tags: Seq[String],
life: Int,
weight: Int
) extends Character
Character.addTag(Person("", "", "", Seq(), 1, ""), "")
// Character.addTag(Beast("", Seq(), 1, 1) // needs implicit as well
This uses the Taggable typeclass that must be implemented by every subclass.
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.
I'm trying to implement a factory design pattern in Scala using the apply methods available on the companion object. I have the following approach.
sealed trait MyType {
def param: String
}
case class TypeA(param: String) extends MyType
case class TypeB(param: String, anotherParam: String) extends MyType
object MyType {
def apply(param: String): TypeA = ???
def apply(param, anotherParam: String): TypeB = ???
}
How do I now force the callers of the above trait to go via the companion object when creating instances of TypeA or TypeB?
You can move the case classes inside the companion object, and set the constructors to be private and accessed only within the companion object.
sealed trait MyType {
def param: String
}
object MyType {
case class TypeA private[MyType] (param: String) extends MyType
case class TypeB private[MyType] (param: String, anotherParam: String) extends MyType
def apply(param: String): TypeA = TypeA(param)
def apply(param: String, anotherParam: String): TypeB = TypeB(param, anotherParam)
}
No one would be able to instantiate the case classes directly, unless though reflection.
scala> MyType("Test")
res0: MyType.TypeA = TypeA(Test)
scala> MyType("Test", "another test")
res1: MyType.TypeB = TypeB(Test,another test)
scala> MyType.TypeA("test??")
<console>:12: error: constructor TypeA in class TypeA cannot be accessed in object $iw
MyType.TypeA("test??")
^
You can simply call the apply method of the case classes themselves. There doesn't seem to be a way to prevent client code from calling TypeA.apply directly, though, as that would prevent MyType from calling it.
sealed trait MyType {
def param: String
}
case class TypeA(param: String) extends MyType
case class TypeB(param: String, anotherParam: String) extends MyType
object MyType {
def apply(param: String): TypeA = TypeA(param)
def apply(param: String, anotherParam: String): TypeB = TypeB(param, anotherParam)
}
The trait MyType is sealed. That me others can do something like new MyType{} to instantiate it.
Then you can remove the case classes.
// No more public case classes TypeA & TypeB
object MyType {
def apply(p: String): MyType = /* case A */ new MyType { val param = p }
private case class InternalB(param: String, other: String) extends MyType
def apply(param: String, anotherParam: String): MyType = InternalB(param, anotherParam)
}
At this point, it's required to use companion object to create MyType instances.
Then you can restore pattern matching for these different cases.
object MyType {
// the apply functions, plus extractors thereafter...
/** Extracts mandatory parameter whatever is the case. */
def unapply(t: MyType): Option[String] = Some(t.param)
/** Extracts both parameter, extra parameter for case B, None for other */
def unapply(t: MyType): Option[(String, String)] = t match {
case InternalB(mandatory, extra)/* Only possible there as private */ =>
Some(mandatory -> extra)
case _ => None
}
}
// Then pattern matching can do...
val test1: Boolean = MyType("A") match {
case MyType(param) => true
case _ => false
}
// Will be true
val test2: Boolean = MyType("B", "extraB") match {
case MyType(param, extra) => true
case _ => false
}
// Will be true
val test3: Int = MyType("A") match {
case MyType(param, extra) => 2
case MyType(param) => 1
case _ => 0
}
// Will be 1
val test4: Boolean = MyType("B", "extraB") match {
case MyType(param) => true
case _ => false
}
// Will be true
It allows a full control over instantiation, and abstraction over implementation of cases.
Is it possible to preserve type information in message handler partial function?
I have partial function eventHandler that matches event by some specific parameters:
def eventHandler: Receive = {
case event: Event ⇒
...
val matchingReactions = projectConfiguration.reactions.filter(reaction ⇒ reaction.eventSelector.matches(event))
Where matches method validates event against a set of rules through reflection:
case class EventSelector(ops: List[FieldEventSelectorOp]) {
def matches[T <: Event](event: T)(implicit tag: ru.TypeTag[T], classtag: ClassTag[T]): Boolean = {
ops.map {
op ⇒ op.matches(event)
}.reduceLeft(_ & _)
}
}
case class FieldEventSelectorOp(field: String, operation: Symbol, value: Any) {
def matches[T <: Event](event: T)(implicit tag: ru.TypeTag[T], classtag: ClassTag[T]): Boolean = {
...
}
So, when I check what is the TypeTag in matches method it returns just Event, not subclass of event - how do I make it pass full type information?
Update:
case class hierarchy for events:
trait Event {
def eventType: String
def eventName: String = this.getClass.getSimpleName
}
trait VCSEvent extends Event {
def eventType: String = "VCS"
}
case class BranchAdded(branch: String) extends VCSEvent
case class TagAdded(tag: String, commitId: String) extends VCSEvent
Concrete matcher:
case class FieldEventSelectorOp(field: String, operation: Symbol, value: Any) extends EventSelectorOp {
def matches[T <: Event](event: T)(implicit tag: ru.TypeTag[T], classtag: ClassTag[T]): Boolean = {
val mirror = ru.runtimeMirror(event.getClass.getClassLoader)
val memberSymbol = tag.tpe.member(ru.newTermName(field))
if (memberSymbol.name.decoded.equals("<none>"))
return false
val fieldValue = if (memberSymbol.isMethod) {
mirror.reflect(event).reflectMethod(memberSymbol.asMethod).apply()
} else {
mirror.reflect(event).reflectField(memberSymbol.asTerm).get
}
operation match {
case 'eq ⇒ fieldValue.equals(value)
case _ ⇒ false
}
}
}
TypeTags describe types which exist only at compile time: at runtime the types are erased, all you get is available via event.getClass. If you want to pass generic type information into the actor, then there is exactly one way to do it: within the message.
trait Event[T] {
def typeTag: ru.TypeTag[T]
...
}
case class MyEvent[T](...)(implicit val typeTag: ru.TypeTag[T])