I want to be able to refer to a list that contains subtypes and pull elements from that list and have them implicitly casted. Example follows:
scala> sealed trait Person { def id: String }
defined trait Person
scala> case class Employee(id: String, name: String) extends Person
defined class Employee
scala> case class Student(id: String, name: String, age: Int) extends Person
defined class Student
scala> val x: List[Person] = List(Employee("1", "Jon"), Student("2", "Jack", 23))
x: List[Person] = List(Employee(1,Jon), Student(2,Jack,23))
scala> x(0).name
<console>:14: error: value name is not a member of Person
x(0).name
^
I know that x(0).asInstanceOf[Employee].name but I was hoping there was a more elegant way with types. Thanks in advance.
The best way is to use pattern matching. Because you are using a sealed trait the match will be exhaustive which is nice.
x(0) match {
case Employee(id, name) => ...
case Student(id, name, age) => ...
}
Well, if you want the employees, you could always use a collect:
val employees = x collect { case employee: Employee => employee }
Related
I shall explain my question with example as shown below.
import scala.reflect.ClassTag
trait LivingBeing extends Product { def name:String; def age:Int}
case class Person (name:String, age:Int) extends LivingBeing
case class Cat(name: String, age:Int) extends LivingBeing
// usual way of creating a case class instance
val john = Person("john", 23)
// Creating a case class instance with tuples
val garfield = Cat tupled ("Garfield", 8)
// create a generic function
def createLivingBeing[T<: LivingBeing](name:String, age:Int)(implicit evidence: ClassTag[T]): T = {
T tupled (name, age) // Does not compile; why?
}
How can one elegantly construct different case classes (that are of a certain trait) generically, given a type and values for its fields?
Consider type class solution
trait LivingBeingFactory[T <: LivingBeing] {
def apply(name: String, age: Int): T
}
object LivingBeingFactory {
implicit val personFactory: LivingBeingFactory[Person] =
(name: String, age: Int) => Person(name, age)
implicit val catFactory: LivingBeingFactory[Cat] =
(name: String, age: Int) => Cat(name, age)
}
def createLivingBeing[T <: LivingBeing](name:String, age:Int)(implicit evidence: LivingBeingFactory[T]): T = {
evidence(name, age)
}
createLivingBeing[Person]("Picard", 70)
// res0: Person = Person(Picard,70)
createLivingBeing[Cat]("Bob", 5)
// res1: Cat = Cat(Bob,5)
// Creating a case class instance with tuples
val garfield = Cat tupled ("Garfield", 8)
...which is the equivalent of...
val garfield = (Cat.apply _).tupled(("Garfield", 8))
This, on the other hand...
T tupled (name, age) // Does not compile; why?
...produces Error: not found: value T because T is a type, not a value. Cat is both a type and a value. It is the type specified for the class, but it is also the companion object to the class Cat. All case classes have a companion object with an apply() method. The compiler knows the difference between them and it knows where one can be used/referenced but not the other.
I have a trait like this:
trait Identifiable {
def id: Option[Long]
}
and then there are some other case classes which extend the Identifiable trait.
for example:
case class EntityA(id: Option[Long], name: String, created: Date) extends Identifiable
case class EntityB(id: Option[Long], price: Long, count: Int) extends Identifiable
assume that I have a Seq[Identifiable] and I want to assign new id to each one.
the simplest approach seems to be:
val xs: Seq[Identifiable] = ...
xs.map {
case x: EntityA => x.copy(id = Some(nextId))
case x: EntityB => x.copy(id = Some(nextId))
}
good! but there's is a problem.
The more subclasses, The more (duplicate) code to be written.
I tried to get help from Union Types:
xs.map {
case x: EntityA with EntityB => x.copy(id = Some(nextId))
}
or
xs.map {
case x # (_: EntityA | _: EntityB) => x.copy(id = Some(nextId))
}
but I got an error that says: Cannot resolve symbol copy
Any help would be appreciated.
Thanks.
Basically, what we want to do here is abstract over the actual type. The problem with that is copy is only implemented OOTB in terms of case classes, and Identifiable is a trait, so there may or may not be a copy method available at compile time, hence why the compiler is yelling at you.
Heavily inspired by this answer, I modified the provided example which uses Shapeless lenses:
import shapeless._
abstract class Identifiable[T](implicit l: MkFieldLens.Aux[T, Witness.`'id`.T, Option[Long]]){
self: T =>
final private val idLens = lens[T] >> 'id
def id: Option[Long]
def modifyId(): T = idLens.modify(self)(_ => Some(Random.nextLong()))
}
case class EntityA(id: Option[Long], name: String, create: Date) extends Identifiable[EntityA]
case class EntityB(id: Option[Long], price: Long, count: Int) extends Identifiable[EntityB]
And now, we can modify each id on any type extending Identifable[T] for free:
val xs: Seq[Identifiable[_]] = Seq(EntityA(Some(1), "", new Date(2017, 1, 1)), EntityB(Some(2L), 100L, 1))
val res = xs.map(_.modifyId())
res.foreach(println)
Yields:
EntityA(Some(-2485820339267038236),,Thu Feb 01 00:00:00 IST 3917)
EntityB(Some(2288888070116166731),100,1)
There is a great explanation regarding the individual parts assembling this answer in the provided link above by #Kolmar, so first and foremost go read the details of how lensing works for the other answer (which is very similar), and then come back to this for a reference of a minimal working example.
Also see #Jasper-M answer here for more ways of accomplishing the same.
Union types aren't the right path here. Consider:
xs.map {
case x # (_: EntityA | _: EntityB) => x.copy(id = Some(nextId))
}
When you say EntityA | EntityB Scala will try and find the supertype that holds these two types together. In this case that is Identifiable, which does not have the copy method and therefore the compiler can't resolve it.
Next:
xs.map {
case x: EntityA with EntityB => x.copy(id = Some(nextId))
}
When you say EntityA with EntityB you're saying "x is a type that is both an EntityA and EntityB at the same time". No such type exists and certainly not one that has a copy method on it.
Unfortunately, I don't think you can generically abstract over the copy method the way you're looking to do in plain Scala. I think your best bet is to add a copy method to your trait and implement methods in each of your sub-classes like so, which unfortunately means some boilerplate:
trait Identifiable {
def id: Option[Long]
def copyWithNewId(newId: Option[Long]): Identifiable
}
case class EntityA(id: Option[Long], name: String) extends Identifiable {
override def copyWithNewId(newId: Option[Long]) = this.copy(id = newId)
}
case class EntityB(id: Option[Long], count: Int) extends Identifiable {
override def copyWithNewId(newId: Option[Long]) = this.copy(id = newId)
}
This is more or less with your working pattern matching, except moving the copy call into the entities themselves.
Now this only applies to plain Scala. You can used more advanced libraries, such as Shapeless or Monocle to do this. See this answer which is pretty similar to what you're trying to do:
Case to case inheritence in Scala
Say I have a model like:
case class User(
id: Int,
name: String,
userType: Int)
Should I then do this:
sealed trait UserType
case object Member() extends UserType
case object Anonymous() extends UserType
I should also somehow associate a value for each UserType.
I would then change the User case class to have a UserType property instead of the int?
I guess then I would create a implicit converter for slick, which I believe would be a MappedColumnType from int to UserType.
Update
This is for using slick db access.
I would do it the other way around. I would have a type for the user depending on the scenario that extends User:
sealed trait User
case class NormalUser(name: String, id: Int) extends User
case class SuperUser(name: String, id: Int, superPowers: Map[String, String]) extends User
And then pattern match on the actual User type when needed.
I'd go with enum:
object UserType extends Enumeration {
type UserType = Value
val Member = Value("Member")
val Anonymous = Value("Anonymous")
}
And the converter as you said:
implicit val userTypeColumnType = MappedColumnType.base[UserType, String](
userType => userType.toString,
UserType.withName
)
Then you can use userType: UserType in your User case class.
In table definition you can have def userType = column[UserType]("user_type")
Update
One of the reasons for choosing enum over trait is that slick is not able to find this implicit converter when you do not put the supertype explicitly.
E.g.
.filter(_.userType === Member)
yields
type mismatch;
[error] found : Member.type
[error] required: slick.lifted.Rep[?]
[error] .filter(_.userType === Member).result
But the following works
.filter(_.userType === Member.asInstanceOf[UserType])
.filter(_.userType === (Member : UserType))
As #Michal Tomanski mentioned below - there are certain problems when using trait / case objects. What you would need to do is this:
sealed trait UserType {
val code: Int
}
case object Member extends UserType {
override val code: Int = 0
}
case object Anonymous extends UserType {
override val code: Int = 1
}
object UserType {
def byId(id: Int): UserType = id match {
case Member.code => Member
case Anonymous.code => Anonymous
case _ => throw new IllegalArgumentException("...")
}
}
implicit val enumColumnType = MappedColumnType.base[UserType, Int](
e => e.code,
i => UserType.byId(i)
)
Above would allow you to do query like this:
UserTable
.filter(_.userType === (Member :: UserType))
.result
This is precisely what #Michal Tomanski pointed out. You can do small trick however to smoothen this up a little bit.
Just modify your trait like this:
sealed trait UserType {
val code: Int
// I added field below
val base: UserType = this
}
and then you can have your query like this:
UserTable
.filter(_.userType === Member.base)
.result
It may be slightly better alternative than casting.
Other than that - I'd follow #Michal Tomanski answer (using Enumeration) assuming that it is sufficient for your case (perhaps solution with trait / case objects is more flexible, but on the other hand there is more plumbing you need to do as can be seen in this answer).
If I have case class defined as below
case class Calculator(brand: String, model: String)
How does it's companion object's unapply method would look like? What type of arguments would it take?
I am not able to emulate this by defining a class and then it's companion object by myself.
class abc (age:Int, name:String) {
}
object abc {
def apply(age:Int, name:String) = new abc(age, name)
def unapply(obj:abc) = Some("test")
}
abc(1, "aaaa")
res6: abc = abc#269f4bad
res6 match {
| case abc(1, "aaaa") => println("found")
| }
error: too many patterns for object abc offering String: expected 1, found 2
case abc(1, "aaaa") => println("found")
^
<console>:14: error: type mismatch;
found : Int(1)
required: String
case abc(1, "aaaa") => println("found")
You'll need to make age and name members of your class so that they are accessible after construction (can be done by making them vals), and then use them in unapply:
class abc (val age:Int, val name:String)
object abc {
def apply(age:Int, name:String) = new abc(age, name)
def unapply(candidate: abc) = Some((candidate.age, candidate.name))
}
Which would match correctly:
scala> new abc(2, "bbbb") match {
| case abc(1, "aaaa") => println("found 1")
| case abc(2, "bbbb") => println("found 2")
| case _ => println("not found")
| }
found 2
Tzach beat me to it..
Your error message btw. is because of the mismatch between the Option your unapply returns (which contains a single string) and the match in your case statement (with wants to match against a pair).
You could include a null check just to make sure (the compiler does, for case class companion objects):
class Abc(val age: Int, val name: String)
object Abc {
def unapply(obj: Abc): Option[(Int, String)] =
if (obj == null)
None
else
Some((obj.age, obj.name))
}
Shameless self-promotion: If you're interested in more details of pattern matching, you might find my little presentation "Everything you always wanted to know about pattern matching" useful.
If you want to create a custom class which has a unapply method and want to use the Extractor Pattern with it, the following rules should apply:
The return type of an unapply should be chosen as follows:
If it is just a test, return a Boolean. For instance case even()
If it returns a single sub-value of type T, return an Option[T]
If you want to return several sub-values T1,...,Tn, group them in an optional
tuple Option[(T1,...,Tn)].
Generally, this means that for you example all that needs to be done with the unapply method is the third option, which returns a tuple of values. Following that, unlike the case class which automatically creates immutable fields for you, you'll need to add the val annotation to you class declaration.
class abc (val age: Int, val name: String)
And:
def unapply(obj: abc): Option[(Int, String)] = Some((obj.age, obj.name))
i have following class hierarchy.
trait Item {val id: String}
case class MItem(override val id: String, val name: String) extends Item
class DItem(override val id: String, override val name: String, val name2: String) extends MItem(id, name)
val d = new DItem("1", "one", "another one")
println(d)
Expected Output
DItem(1, one, another one)
Actual Output
Mitem(1,one)
Why is this happening. What is recommended so that i get the real type of my object and the not type of super class.
This is not a type erasure. You are getting this result because DItem gets toString() implementation inherited from Mitem. You have to override it to get what you want
class DItem(override val id: String, override val name: String, val name2: String) extends MItem(id, name) {
override def toString = s"DItem($id, $name, $name2)"
}
So here is a result:
scala> val d = new DItem("1", "one", "another one")
d: DItem = DItem(1, one, another one)
scala> println(d)
DItem(1, one, another one)
It is almost always a bad idea to inherit from case classes because besides toString successor class will also inherit equals and hashCode.
Another drawback is limited pattern-matching for such successor classes i.e it is impossible to use such classes in case branches and may lead to confusing errors.
Example
case class A(id: String)
class B(id: String, name: String) extends A(id)
new B("foo", "bar") match {
case A(id) => println(id)
case other => println(other)
}
You may expect that there is no error in this code, but you'll get
<console>:17: error: constructor cannot be instantiated to expected type;
found : A
required: B
case A(id) => println(id)
^
However if you'll infer a type for B instance explicitly it will work
scala> new B("foo", "bar").asInstanceOf[A] match {
| case A(id) => println(id)
| case other => println(other)
| }
foo
So... Inheriting from case classes is very error-prone and confusing and should be avoided unless you know what are you doing.
Inheriting from case classes is deprecated as far as I know. So case classes can (should) only inherit from regular classes.
doing println usually invoke toString on the object pass on it.
so what happen on your code is, it will invoke the toString implementation of the object, it happens to be MItem that has this implementation.
so you need to override the toString on DItem like this:
class DItem(override val id: String, override val name: String, val name2: String) extends MItem(id, name) {
override def toString = s"DItem($id, $name, $name2)"
}
if you want to just get the type of the object you can use getClass.
println(d.getClass)