Better solution to case class inheritance deprecation? - scala

I am working through the (admittedly somewhat old) Programming Scala [Subramaniam, 2009] and encountered a troubling compiler warning when working through section 9.7 "Matching using case classes" (see below).
Here is the solution I devised based on my interpretation of the error message. How could I improve upon this code with a solution closer to the original intent of the book's example? Particularly if I wanted to use sealed case class functionality?
/**
* [warn] case class `class Sell' has case ancestor `class Trade'.
* Case-to-case inheritance has potentially dangerous bugs which
* are unlikely to be fixed. You are strongly encouraged to instead use
* extractors to pattern match on non-leaf nodes.
*/
// abstract case class Trade()
// case class Buy(symbol: String, qty: Int) extends Trade
// case class Sell(symbol: String, qty: Int) extends Trade
// case class Hedge(symbol: String, qty: Int) extends Trade
object Side extends Enumeration {
val BUY = Value("Buy")
val SELL = Value("Sell")
val HEDGE = Value("Hedge")
}
case class Trade(side: Side.Value, symbol: String, qty: Int)
def process(trade: Trade) :String = trade match {
case Trade(_, _, qty) if qty >= 10000 => "Large transaction! " + trade
case Trade(_, _, qty) if qty % 100 != 0 => "Odd lot transaction! " + trade
case _ => "Standard transaction: " + trade
}

Inherit from a "sealed trait Trade" instead.
sealed trait Trade
case class Buy(symbol: String, qty: Int) extends Trade
case class Sell(symbol: String, qty: Int) extends Trade
case class Hedge(symbol: String, qty: Int) extends Trade

Related

Scala 2.13 Case Class: Union of multiple case classes?

I have 3 case classes in Scala:
case class Teacher(name: String, subject: String, age: Int)
case class Student(name: String, subject: String, section: String, class: Int)
case class School(user: Teacher | Student)
In the third case class, the User could either be Teacher or Student but it has to be only one of them. How can I achieve the same syntactically in Scala 2.13?
Edit 1: As suggested in comments, Either does seem convenient but what if:
case class School(user: Teacher | Student | Principal)
Edit 2:
If I use a sealed trait as suggest in comments:
sealed trait SchoolUser
case class Student(name: String, subject: String, section: String, `class`: Int)
case class Teacher(name: String, subject: String, age: Int)
case class Principal(name: String, year: Int)
case object Student extends SchoolUser
case object Teacher extends SchoolUser
case object Principal extends SchoolUser
case class School(user: SchoolUser)
val school = School(user = ???)
println("Complete")
But I am confused on how to instantiate the case class as in line:
val school = School(user = ???) // Principal(name="ABC",year=2022)
Edit 4: Finally got all the answers
As suggested in the comments, for two cases Either works fine however for Three or More an ADT would suit with a sealed trait:
sealed trait SchoolUser
case class Student(name: String, subject: String, section: String, `class`: Int) extends SchoolUser
case class Teacher(name: String, subject: String, age: Int) extends SchoolUser
case class Principal(name: String, year: Int) extends SchoolUser
case class School(user: SchoolUser)
val schoolPrincipal:SchoolUser = Principal("Name",123)
println("Complete")

Extend case class from another case class

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

Filter with type on generics type

I have the following code (simplified):
sealed trait Funding {
id: Int
}
final case class FlatRate(id: Int, days: Int, amount: BigDecimal) extends Funding
final case class PerItem(id: Int, amount: BigDecimal, discount: BigDecimal) extends Funding
final case class AppliedFunding[+A <: Funding](
person: String
)
I have some code that returns a list of "generic" AppliedFunding and I would like to get a specific AppliedFunding with the correct type. Pseudo-code:
val allFunding: List[AppliedFunding[Funding]] = <...>
val flatrateFunding: List[AppliedFunding[FlatRate]] = allFunding.someMagicFunction
In place of someMagicFunction I am looking for a way to filter the correct AppliedFunding instances out and return them with the specific type. I can do allFunding.collect { ... }, but no matter what I tried it would still return List[AppliedFunding[Funding]].
Any help is much appreciated.
below are 2 ways how to do it. Just a sideremark: collect is one of the very few situations where PartialFunctionis a good thing
sealed trait Foo
final case class Bar(i:Int) extends Foo
final case class Baz(d:Double) extends Foo
final case class Container[F <: Foo](s:String, f:F)
val all:List[Container[Foo]] = List(
Container("bar", Bar(3)),
Container("bar", Bar(4)),
Container("bar", Bar(5)),
Container("bar", Bar(6)),
Container("bar", Bar(7)),
Container("baz", Baz(1.5)),
Container("baz", Baz(2.5)),
Container("baz", Baz(3.5)),
Container("baz", Baz(4.5)),
Container("baz", Baz(5.5))
)
val bars:List[Container[Bar]] = all.collect { case Container(s, Bar(i)) => Container(s, Bar(i))}
val baz:List[Container[Baz]] = all.foldRight(List.empty[Container[Baz]]) (
(elem, lst) => elem match {
case Container(s, Baz(d)) => Container(s, Baz(d)) :: lst
case _ => lst
}
)
println(bars)
println(baz)
Below is a solution on how this can be done with help of isInstanceOf and asInstanceOf. Another working solution is provided by Dominic in other answer, but for filtering use case I am not sure that creating a new case class instance for each item which passes the filter is a proper way to go. Moreover, desugaring with scalac -print shows that isInstanceOf invocation is not going anywhere in that case, it will be embedded in generated isDefinedAt and applyOrElse methods for the partial function used in collect.
Some people say that using isInstanceOf and asInstanceOf is a code smell but for me it looks that in this particular case usage is safe and correct and brings no single benefit over the solution with collect: invocation of collect just hides isInstanceOf in the generated source code and instantiating new case classes instances is a trade off for a safe call of asInstanceOf on a filtered collection.
Also please pay attention that I've added a funding field to the AppliedFunding case class. I think that having AppliedFunding instances without embedded info about funding itself is not very useful anyway.
object Test {
sealed trait Funding {
val id: Int
}
final case class FlatRate(id: Int, days: Int, amount: BigDecimal) extends Funding
final case class PerItem(id: Int, amount: BigDecimal, discount: BigDecimal) extends Funding
final case class AppliedFunding[+A <: Funding](
person: String, funding: A)
val allFundings: List[AppliedFunding[Funding]] = List(AppliedFunding[FlatRate]("alex", FlatRate(1, 10, 100)), AppliedFunding[PerItem]("christian", PerItem(2, 100, 10)))
def flatRatePredicate[A <: Funding](appliedFunding: AppliedFunding[A]): Boolean = appliedFunding.funding.isInstanceOf[FlatRate]
val flatRateFundings: List[AppliedFunding[FlatRate]] = allFundings.filter(flatRatePredicate).asInstanceOf[List[AppliedFunding[FlatRate]]]
def main(args: Array[String]): Unit = {
println(flatRateFundings)
}
}

Scala - union types in pattern matching

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

How to make my handle method polymorphic when using traits

I want my handle method to be able to take any Animal type and then be able to actually have the specific type so I can continue processing.
I just sketched out what I am looking for:
sealed trait Animal
case class Dog(id: Int) extends Animal
case class Cat(id: Int) extends Animal
case class Owl(id: Int) extends Animal
object AnimalHandler {
def handle(animal: Animal) = animal match {
case Dog => processDog(dog)
case Cat => processCat(cat)
}
}
How can I design my domain to work like the above?
The code you have pretty much works as-is.
The syntax on the pattern match would need to include a variable name, and if you're actually using the attributes of the case classes you would probably use the pattern to extract them.
sealed trait Animal
case class Dog(id: Int) extends Animal
case class Cat(id: Int) extends Animal
case class Owl(id: Int) extends Animal
def processDog(dog: Dog) = println(s"woof ${dog.id}")
def processCat(id: Int) = println(s"meow $id")
def processOwl(owl: Owl, id: Int) = println(s"hoot ${owl.id}")
object AnimalHandler {
def handle(animal: Animal) = {
case dog: Dog => processDog(dog)
case Cat(id) => processCat(id)
case owl # Owl(id) => processOwl(owl, id)
}
}
Some notes:
given a sealed trait, the compiler will warn you if your pattern match isn't complete (in your case you were missing handling for Owl).
if the body of a method/function is nothing but a pattern match on the argument you can skip the animal match