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
Related
I have two case class Person and Employee
case class Person(identifier: String) {}
case class Employee (salary: Long) extends Person {}
I am getting following error:
Unspecified value parameters: identifier: String
Error: case class Employee has case ancestor Person, but case-to-case inheritance is prohibited. To overcome this limitation, use extractors to pattern match on non-leaf nodes
I am new to Scala and not able to understand what I have to do.
Version:
Scala : 2.11
Unfortunately, I'm afraid it is not possible for case class to extend another case class.
The inheritance in "plain" classes would look like:
class Person(val identifier: String) {}
class Employee(override val identifier: String, salary: Long)
extends Person(identifier) {}
val el = new Employee("abc-test", 999)
println(el.identifier) // => "abc-test"
If you would like to achieve a similar effect with case classes, you would need to reach out to traits:
trait Identifiable {
def identifier: String
}
case class Person(identifier: String) extends Identifiable {}
case class Employee(identifier: String, salary: Long)
extends Identifiable {}
val el = Employee("abc-test", 999)
println(el.identifier) // => "abc-test"
Defining extractors
Extractor provides a way for defining a matching statement used in pattern matching. It is defined in an object in unaply method.
Let's consider the first example again adding support for extractors:
class Person(val identifier: String)
class Employee(override val identifier: String, val salary: Long)
extends Person(identifier)
object Person {
def unapply(identifier: String): Option[Person] = {
if (identifier.startsWith("PER-")) {
Some(new Person(identifier))
}
else {
None
}
}
}
object Employee {
def unapply(identifier: String): Option[Employee] = {
if (identifier.startsWith("EMP-")) {
Some(new Employee(identifier, 999))
}
else {
None
}
}
}
Now, let's define a method that will define pattern matching using those extractors:
def process(anInput: String): Unit = {
anInput match {
case Employee(anEmployee) => println(s"Employee identified ${anEmployee.identifier}, $$${anEmployee.salary}")
case Person(aPerson) => println(s"Person identified ${aPerson.identifier}")
case _ => println("Was unable to identify anyone...")
}
}
process("PER-123-test") // => Person identified PER-123-test
process("EMP-321-test") // => Employee identified EMP-321-test, $999
process("Foo-Bar-Test") // => Was unable to identify anyone...
Case classes in Scala add several different features but often you really use only some of them. So the main question you need to answer is which features you really need. Here is a list based on the spec:
remove the need to type val before field names/constructor params
remove the need for new by adding apply method to the companion object
support for pattern matching by adding unapply method to the companion object. (One of nice things of Scala is that pattern-matching is done in a non-magical way, you can implement it for any data type without requiring it to be a case class)
add equals and hashCode implementations based on all the fields
add toString implementations
add copy method (useful because case classes are immutable by default)
implement Product trait
A reasonable guess of the equivalent for case class Person(identifier: String) is
class Person(val identifier: String) extends Product {
def canEqual(other: Any): Boolean = other.isInstanceOf[Person]
override def equals(other: Any): Boolean = other match {
case that: Person => (that canEqual this) && identifier == that.identifier
case _ => false
}
override def hashCode(): Int = identifier.hashCode
override def toString = s"Person($identifier)"
def copy(newIdentifier: String): Person = new Person(newIdentifier)
override def productElement(n: Int): Any = n match {
case 0 => identifier
case _ => throw new IndexOutOfBoundsException(s"Index $n is out of range")
}
override def productArity: Int = 1
}
object Person {
def apply(identifier: String): Person = new Person(identifier)
def unapply(person: Person): Option[String] = if (person eq null) None else Some(person.identifier)
}
case class Employee(override val identifier: String, salary: Long) extends Person(identifier) {}
Actually the main objections to inheriting from a case class and especially making a case class inheriting another one are the Product trait, copy and equals/hashCode because they introduce ambiguity. Adding canEqual partially mitigates the last problem but not the first ones. On the other hand in a hierarchy like yours, you probably don't need the copy method or Product implementation at all. If you don't use Person in pattern matching, you don't need unapply as well. Most probably all you really need case for is apply, toString and hashCode/equals/canEqual.
Inheriting from case classes (even with regular non-case classes, which is not prohibited) is a bad idea. Check this answer out to get an idea why.
You Person does not need to be a case class. It actually does not need to be a class at all:
trait Person {
def identifier: String
}
case class Employee(identifier: String, salary: Long) extends Person
In my code base, I want to refractor away from vars. The code base structure follows the format:
class Animal {
var name : Option[String] = None
var owner : Option[String] = None
}
case class Dog(breed: String) extends Animal {
//Dog logic
}
The main reason for this design is that information is not available all at the same time.
So, from service A, which deserializes a json, I receive an Animal (either Animal, Dog)
val animal = new Animal
animal.name = "My little animal"
and then
def update(animal: Animal) : Unit {
animal match {
case a : Animal => a.owner = Some("Joe") // call to service B
case _ => //handle dog
}
}
update(animal)
The question is: How can I redesign it to avoid mutable state in Animal?
Write a copy method in Animal? There are a bunch of fields, which might generate some boilerplate
Composition?
Making the two classes case?
case class Animal (...)
case class Dog(animal: Animal, breed: String)
Edit
Animal as a trait
I still need a concrete implementation of Animal, as I will have Dogs and Animals (which are really Animal and no subtype)
Problems with copy
The built in case class copy method, does NOT update the values of the Animal class. So defaults values will be used - not what I want. - yes, it is possible to create a case class with all the fields, but is it practical if we have 100+ fields?
You could design this as an ADT (Abstract Data Type) like this:
trait Animal {
def name: Option[String]
def owner: Option[String]
}
case class Dog(dog: Option[String] = None, owner: Option[String] = None) extends Animal
With a case class you have the copy method by default!
A good solution could be to make a trait which specifies Animal behavior,
sealed trait Animal {
val name: Option[String]
val owner: Option[String]
}
and then make case classes to provide type constructors for your animal instances.
case class Dog(name: Option[String], owner: Option[String]) extends Animal
Now, update (changeOwner) will take an Animal and return another Animal likewise:
def changeOwner(animal: Animal): Animal = animal match {
case Dog(name, owner) => Dog(name, Some("Joe"))
case _ => animal
}
And you will use it as follows:
val dog: Animal = Dog(Some("Doggie"), Some("Jack"))
val newDog: Animal = changeOwner(dog)
Also, from your code:
case a : Animal => a.owner = Some("Joe") // call to service B
Be careful with this, as the first case of your match will take all animals and thus the other cases, for example, your Dog case, will be unreachable.
I have an abstract base class, Foo, whose constructor I'd like to have an optional parameter. If none is provided, I'll just give it a None value.
A source Foo will not have parents, so I'd just like to construct them without a list of parents (leave default value for parent list)
A derived Foo might have provided parents, so I'd like to mimic the signature of the Foo base class.
Below is my attempt:
abstract class Foo(val id: String, var parentIds: Option[List[String]]=None) { }
case class SourceFoo(override val id: String)
extends Foo(id, parentIds=None) { }
case class DerivedFoo(override val id: String,
override var parentIds: Option[List[String]])
extends Foo(id, parentIds) { }
I'm getting a compiler error that a mutable variable cannot be overridden (referencing the parentIds in the DerivedFoo constructor.
This list is subject to change, so I don't want to make it a val (which removes my compiler issues).
This is a very basic OO issue, so it must be simpler than I seem to be making it. How can I achieve my desired behavior idiomatically?
I managed to fix this after reading the documentation:
The constructor parameters of case classes are treated as public values and can be accessed directly.
Since my base class is abstract, I can simply extend it with default, val construction.
I simply need to specify that parentIds is a var in the DerivedFoo constructor.
abstract class Foo(id: String, parentIds: Option[List[String]]=None) { }
case class SourceFoo(id: String) extends Foo(id) { }
case class DerivedFoo(id: String, var parentIds: Option[List[String]]=None)
extends Foo(id, parentIds) { }
Here is another probably better way to go about it. Explictly acknowledge the difference between class parameters and class members. You also can make them private members if you like following this block of code.
abstract class Foo(identifier: String, parentIdentifiers: Option[List[String]]) {
val id = identifier
var parentIds = parentIdentifiers
}
case class SourceFoo(override val id: String) extends Foo(id, parentIdentifiers = None) { }
case class DerivedFoo(identifier: String, parentIdentifiers: Option[List[String]]) extends Foo(identifier, parentIdentifiers) { }
After that, you can create DerivedFoo and refer to the members as you are probably expecting, and you won't have two members with different names.
REPL output:
scala> DerivedFoo("1", Some(List("200","201","202")))
res0: DerivedFoo = DerivedFoo(1,Some(List(200, 201, 202)))
scala> res0.parentIds
res1: Option[List[String]] = Some(List(200, 201, 202))
scala> res0.parentIds = Some(List("800", "801", "802"))
res0.parentIds: Option[List[String]] = Some(List(800, 801, 802))
I think you can achieve your goal by changing the name of the parameter in the abstract class as follows.
abstract class Foo(val id: String, var parentIdentifiers: Option[List[String]]) {
parentIdentifiers = None
}
case class SourceFoo(override val id: String)
extends Foo(id, parentIdentifiers = None) { }
case class DerivedFoo(override val id: String,
var parentIds: Option[List[String]])
extends Foo(id, parentIds) { }
For the mutation, you can import scala.collection.mutable and use mutable.ListBuffer instead of List.
I assume of course, that you won't change the parentIds of a DerivedFoo instance from Some to None.
This will allow you use vals but still have mutable state.
But I wouldn't say mutable state is idiomatic Scala.
You usually use immutable val and List, and just copy the object whenever you want to change the list.
val fooA = SourceFoo("a")
val fooB = DerivedFoo("b", "a" :: Nil)
val fooB2 = fooB.copy(parentIds = fooB.parentIds :+ "x")
So to be more idiomatic, the simplest you can do is
sealed abstract class Foo(val id: String, val parentIdsOpt: Option[List[String]])
case class SourceFoo(override val id: String)
extends Foo(id, None)
case class DerivedFoo(override val id: String, val parentIds: List[String])
extends Foo(id, Some(parentIds))
Which is pretty close to what you had.
Note that DerivedFoo.parentIds isn't Option anymore, because DerivedFoo always has parents, so you don't have to deal with Option. (You still have to deal with empty list, though)
Also note the sealed keyword on the trait, which is not required, but recommended if you want to match on an instance of the abstract class or trait. (You can use sealed only if you own all the sub-classes, which seems to be the case in your example)
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)
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 }