Scala 2.13 Case Class: Union of multiple case classes? - scala

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")

Related

Play JSON with sealed trait / case classes: infinite recursion

I have a code where I try to customize JSON serialization of a bunch of case classes by defining a custom Writes for the base trait. I'm getting infinite recursion / stack overflow.
I created a simplified sample - if somebody knows how to fix it, please let me know.
import play.api.libs.json._
sealed trait Person {
val name: String
}
final case class Teacher(name: String, salary: Int) extends Person
final case class Student(name: String, grade: Int) extends Person
implicit val teacherWrites: Writes[Teacher] = Json.writes[Teacher]
implicit val studentWrites: Writes[Student] = Json.writes[Student]
val ThePersonWrites: Writes[Person] = Writes(person => {
Json.writes[Person].writes(person).as[JsObject] - "_type"
})
implicit val personWrites: Writes[Person] = ThePersonWrites
val people = List[Person] (
Teacher("Jane Doe", 40000),
Student("Alice", 5),
Student("Bob", 7)
)
Json.prettyPrint(Json.toJson(people))
You need play-json-derived-codecs
import play.api.libs.json._
import julienrf.json.derived
sealed trait Person {
val name: String
}
object Person {
implicit val jsonFormat: OFormat[Person] = derived.oformat[Person]()
}
final case class Teacher(name: String, salary: Int) extends Person
final case class Student(name: String, grade: Int) extends Person
val people = List[Person] (
Teacher("Jane Doe", 40000),
Student("Alice", 5),
Student("Bob", 7)
)
println(Json.prettyPrint(Json.toJson(people)))
See here the scalafiddle
This should do it:
import play.api.libs.json._
sealed trait Person {
val name: String
}
final case class Teacher(name: String, salary: Int) extends Person
final case class Student(name: String, grade: Int) extends Person
implicit val teacherWrites: Writes[Teacher] = Json.writes[Teacher]
implicit val studentWrites: Writes[Student] = Json.writes[Student]
implicit val personWrites: Writes[Person] = Writes[Person] {
case t: Teacher => Json.toJson(t)(teacherWrites)
case s: Student => Json.toJson(s)(studentWrites)
}
val people = List[Person] (
Teacher("Jane Doe", 40000),
Student("Alice", 5),
Student("Bob", 7)
)
Json.prettyPrint(Json.toJson(people))
The trick is adding teacherWrites and studentWrites explicitly. Because they are both Persons, before it was recognizing them as such and calling your personWrites again, hence the stack overflow.

Scala. Case class copy from trait reference

Hi I'm trying to solve a problem in kind of "elegant" and type safe way but I can't find the best...
Let's say I have this trait
trait Event {
def deviceId: String
def userId: String
def eventDateTime: DateTime
def payload: Option[Payload]
}
trait Payload
And following case classes (there could be more)
case class AEvent (deviceId: String, userId: String, eventDateTime: DateTime, payload: Option[APayload]) extends Event
case class APayload (content: String)
case class BEvent (deviceId: String, userId: String, eventDateTime: DateTime, payload: Option[BPayload]) extends Event
case class BPayload (size: Int, name: String)
I would like to use case class copy method directly from the trait without casting to AEvent or BEvent...
As I'm having a reference to the trait, best solution I figured out is to create a method like this:
def copy[T <: Event](event: T)(deviceId: String = event.deviceId,
userId: String = event.userId,
eventDateTime: DateTime = event.eventDateTime,
payload: Option[Payload] = event.payload) T = {
val res = event match {
case x: AEvent => AEvent(deviceId, userId, eventDateTime, payload.asInstanceOf[APayload])
case x: BEvent => BEvent(deviceId, userId, eventDateTime, payload.asInstanceOf[BPayload])
}
res.asInstanceOf[T]
}
What I don't like is that Payload type is casted in runtime...
How can I have type check during compile time?
Thanks in advance
What about
case class Event[P <: Payload](deviceId: String, userId: String, eventDateTime: DateTime, payload: Option[P])
and using Event[APayload] instead of AEvent?

Inheritance in case classes

I have the following case class definition:
case class FileMetadata(announceUrls: List[String], pieceLength: Int, pieces: String) {
case class SingleFileMetadata(filename: String, length: Int)
case class MultiFileMetadata(dirname: String, files: List[FileInfo])
}
because I want both the single and multi file types to have all the same data values as the enclosing FileMetaData case class. This doesn't really seem to work though (in that I can't create a new SingleFileMetadata or MultiFileMetaData class.
Is there a way to do this in Scala?
In your current code, SingleFileMetadata and MultiFileMetadata do not inherit from FileMetadata. In fact, they only exist within specific instances of FileMetadata, which you most certainly do not want. Furthermore, you cannot extend a case class, as all of the members are private, so FileMetadata would have to be a class (or trait).
Something like this might work for you:
class FileMetadata(announceUrls: List[String], pieceLength: Int, pieces: String)
case class SingleFileMetadata(filename: String, length: Int, announceUrls: List[String], pieceLength: Int, pieces: String) extends FileMetadata(announceUrls, pieceLength, pieces)
case class MultiFileMetadata(dirname: String, files: List[FileInfo], announceUrls: List[String], pieceLength: Int, pieces: String) extends FileMetadata(announceUrls, pieceLength, pieces)
You can make your base class a trait and make both case classes inherit from it.
trait FileMetadata {
def announceUrls: List[String]
def pieceLength: Int
def pieces: String
}
case class SingleFileMetadata(filename: String, length: Int, announceUrls: List[String], pieceLength: Int, pieces: String) extends FileMetadata
case class MultiFileMetadata(dirname: String, files: List[FileInfo], announceUrls: List[String], pieceLength: Int, pieces: String) extends FileMetadata
One additional advantage of using trait is that you can limit the inheritance to only the classes defined in the given file, by specifying that trait as a sealed trait.
Alternative solution to your problem would be to create only one generic case class, set optional parameters to default value None, with flexible pattern matching you can easily perform operation on this class.
case class GenericFileMetadata(filename:Option[String] = None,
length:Option[String] = None,
dirname:Option[String] = None,
files:Option[List[FileInfo]]= None,
announceUrls: List[String], pieceLength: Int, pieces: String)

Better solution to case class inheritance deprecation?

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

Must override val variable in scala

I meet a weird problem in scala. Following is my code, class Employee extends class Person
But this piece of code can not been compiled, I have explicit define firstName and lastName as val variable. Why is that ? Does it mean I have to override val variable in base class ? And what is the purpose ?
class Person( firstName: String, lastName: String) {
}
class Employee(override val firstName: String, override val lastName: String, val depart: String)
extends Person(firstName,lastName){
}
The input parameters for the constructor are not vals unless you say they are. And if they are already, why override them?
class Person(val firstName: String, val lastName: String) {}
class Strange(
override val firstName: String, override val lastName: String
) extends Person("John","Doe") {}
class Employee(fn: String, ln: String, val depart: String) extends Person(fn,ln) {}
If they're not vals and you want to make vals, you don't need to override:
class Person(firstName: String, lastName: String) {}
class Employee(
val firstName: String, val lastName: String, val depart: String
) extends Person(firstName,lastName) {}
Since the constructor arguments have no val/var declaration in Person, and as Person is no case class, the arguments will not be members of class Person, merely constructor arguments. The compiler is telling you essentially: hey, you said, that firstName and lastName are members, which override/redefine something inherited from a base class - but there is nothing as far as I can tell...
class Person(val firstName: String, val lastName: String)
class Employee(fn: String, ln: String, val salary: BigDecimal) extends Person(fn, ln)
You do not need to declare firstName/lastName as overrides here, btw. Simply forwarding the values to the base class' constructor will do the trick.
You might also consider redesigning your super classes as traits as much as possible. Example:
trait Person {
def firstName: String
def lastName: String
}
class Employee(
val firstName: String,
val lastName: String,
val department: String
) extends Person
or even
trait Employee extends Person {
def department: String
}
class SimpleEmployee(
val firstName: String,
val lastName: String,
val department: String
) extends Employee
Unless I've misunderstood your intention, here's how to extend Person.
Welcome to Scala version 2.8.0.final (Java HotSpot(TM) Client VM, Java 1.6.0_21).
Type in expressions to have them evaluated.
Type :help for more information.
scala> class Person( firstName: String, lastName: String)
defined class Person
scala> class Employee(firstName: String, lastName: String, depart: String) extends Person(firstName, lastName)
defined class Employee