Scala: handle different constructor when extending a parameterised Class - scala

I am writing a scala code, and want to handle different constructor when extending a parameterised Class. For example:
class Person (val name:String, val age: Int)
class Employee(name:String, age:Int, val position:String)
extends Person(name, age)
However, what I want is that Employee can have two constructors one of which takes information of Person to construct, one takes another Employee to construct:
val emply1 = new Employee(yzh, 30, CEO)
val emply2 = new Employee(emply1)
If I want both constructors works, how can I do?
Thanks!

If you want two constructors, you simply write two constructors:
class Person(val name: String, val age: Int)
class Employee(name: String, age: Int, val position: String) extends
Person(name, age) {
def this(e: Employee) = this(e.name, e.age, e.position)
}
val emply1 = new Employee("yzh", 30, "CEO")
val emply2 = new Employee(emply1)

You need to add an axillary constructor.
class Employee(name:String, age:Int, val position:String)
extends Person(name, age) {
def this(emp: Employee) = this(emp.name, emp.age, emp.position)
}
If your Employee were a case class then you could do this instead.
val emply1 = Employee("yzh", 30, "CEO")
val emply2 = emply1.copy()

Related

Scala : Add an implicit method to transform case class to another case class in alias object?

I am new to Scala and I have a case class like this:
case class Student(
name: String,
age: Option[Int] = None,
id: String
)
and another case class like this:
case class Member(
id: String,
`type`: String,
name: String
)
I want to create a helper implicit in such a way that:
case class Member(
id: String,
`type`: String,
name: String
)
object Member {
implicit def toStudent(member:Member):Student = Student(member.name,None,member.id)
}
and then call the method like this:
val newMember = Member("abc","Student","John")
val student = newMember.toStudent
or something fancy like that. Would this be possible in Scala as I see a lot of fancy syntax similar to this at a lot of places?
In Scala 3 you can use extensions methods:
// ...
extension (member: Member)
def toStudent: Student = Student(member.name, None, member.id)
val newMember = Member("abc", "Student", "John")
val student = newMember.toStudent
For Scala 2 you can define explicit conversion:
import scala.language.implicitConversions
object Member {
implicit def toStudent(member: Member): Student =
Student(member.name, None, member.id)
}
val newMember = Member("abc", "Student", "John")
val student = newMember: Student
Or using implicit class decorator:
implicit class MemberDecorator(val member: Member) extends AnyVal {
def toStudent(): Student = Student(member.name, None, member.id)
}
val newMember = Member("abc", "Student", "John")
val student = newMember.toStudent()

Accessing fields of trait of an object instance

I created this little Scala worksheet about inheritance and traits in Scala. As far as I understood, one is able to add traits to single object instances and access the methods of said trait in through that object.
So I mixed in Parents with the f4 object of sister, yet I can't access the methods of Parents. The same goes for f1 and Grandparents.
abstract class FamilyMember(name: String, birthDate: String, placeOfBirth: String) {
override def toString: String = this.name;
}
trait Parents extends FamilyMember{
var children: Array[FamilyMember] = new Array[FamilyMember](0);
def printChildren(): Unit = for (child <- children ) println(child.toString)
def isParent(): Boolean = true
}
trait GrandParents extends Parents {
def printGrandChildren(): Unit = for (child <- children; child2 <- child.asInstanceOf[Parents].children) println(child2.toString)
}
class Mother(name: String, birthDate: String, placeOfBirth: String) extends FamilyMember(name, birthDate, placeOfBirth) with Parents {
}
class Sister(name: String, birthDate: String, placeOfBirth: String) extends FamilyMember(name, birthDate, placeOfBirth) {
}
val f1: Mother = new Mother("Hildegard", "23-7-1952", "Berlin") with GrandParents
val f4: Sister = new Sister("Moni", "19-12-1993", "Frankfurt am Main") with Parents
val f5: Sister = new Sister("Anne", "10-12-2012","Berlin")
//works fine:
f1.isParent
f1.children = f1.children :+ f4
//doesn't work:
f4.isParent
f4.children = f5.children :+ f5
f4.printChildren()
f1.printGrandChildren()
The error I get:
Error:(30, 4) value isParent is not a member of Sister
f4.isParent
But I added it, with the trait.
To be somewhat more explicit: by writing
val f4: Sister = ...
you specify the static type of f4 is Sister and so the compiler will only let you call Sister's methods, whatever ... is (provided it can have type Sister, of course).
As #Luis Miguel Mejía Suárez has pointed out in the comments:
Sister is not a Parent... Try with val f4: Sister with Parent, or
leave type inference do its job. - BTW, a var in a trait is a really
bad idea.
Using the following code it works:
val f1: Mother with GrandParents= new Mother("Hildegard", "23-7-1952", "Berlin") with GrandParents
val f4: Sister with Parents = new Sister ("Moni", "19-12-1993", "Frankfurt am Main") with Parents
val f5: Sister = new Sister("Anne", "10-12-2012","Berlin")

Generic function for case class creation with tuple does not compile

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.

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.

Return a case class that extends a trait

I need to define to return a case class that extends a trait:
trait Id {
def id: Long
}
case class Person(name: String)
val john = Person("John")
val johnWithId: Person with Id = /*person -> 123L*/ ???
Any idea how can I achieve that?
I'm trying to reduce the duplication of code, that's why I didn't declare a trait Person like this:
trait Id {
def id: Long
}
trait Person {
def name: String
}
case class PersonWithoutId(name: String) extends Person
case class PersonWithId(name: String, id: Long) extends Person with Id
val john = PersonWithoutId("John")
val johnWithId: Person with Id = PersonWithId(person.name, 123L)
Any idea how to achieve that?
Once Person is already instantiated, it's too late - you can't change the instance john after it has already been instantiated. You can, however, instantiate a Person with Id:
val johnWithId: Person with Id = new Person("John") with Id {
override def id: Long = 123L
}
Do note that this isn't really equivalent to using a PersonWithId(name: String, id: Long) case class, for example - equals and hashcode would ignore the ID in this implementation.
val johnWithId: Person with Id = new Person("John") with Id { def id = 123L }
A bit round-about way:
case class WithId[A](id: Long, value: A) extends Id
object WithId {
implicit def getValue[A](x: WithId[A]): A = x.value
}
// elsewhere
val johnWithId = WithId(123, john)
johnWithId doesn't extend Person (so e.g. johnWithId.isInstanceOf[Person] is false), but can still be used where a Person is expected and getValue.
The most correct solution would be
case class Person(name: String, id: Long) extends Id
while you could in theory do
val johnWithId: Person with Id = new Person("John") with Id { def id = 123L }
as stated by Andrey, it wouldn't respect the overall case class contract because people with different ids and same name would be equal, since id wouldn't be used on the equals method