Reduce boilerplate in class inheritance - scala

I was wondering if there are tricks to reduce boilerplate code in this scenario: inheriting from a class (let's assume that the real world has many more constructor parameters).
class Person(name: String, surname: String) {
def fullname = name + " " + surname
def header = "Dear " + name
}
class Employee(val aname: String,
val asurname: String,
val role: String) extends Person(aname, asurname) {
}
I have seen some strategies that involved the use of traits.
Having an abstract base class is not an option.

The question is, do you really have to extend the base class? The trait approach you link to may be enough for your case.
That is, make both Person and Employee implement a trait with methods name, surname, fullname and header:
trait PersonLike {
def name: String
def surname: String
def fullname = name + " " + surname
def header = "Dear " + name
}
And have the classes extend the trait:
case class Person(name: String, surname: String) extends PersonLike
case class Employee(name: String, surname: String, role: String) extends PersonLike
Case classes are probably better for this, but you may use normal classes instead:
class Person2(val name: String, val surname: String) extends PersonLike
class Employee2(val name: String, val surname: String, val role: String) extends PersonLike

Using val in a constructor creates new public members. Simplify it using
case class Person( name: String, surname: String ) {
def fullname = name + " " + surname
def header = "Dear " + name
}
class Employee( _name: String, _surname: String, val role: String )
extends Person( _name, _surname )
I also changed Person to be a case class, so the arguments of the constructor become public fields.
If you're looking to reduce the boilerplate, I think that using traits or other tricks simply add more code. So, keep it simple is my suggestion.

Related

Type safe Scala Builder pattern with special rules

I'm trying to create a type safe builder of a case class , where its params can be of following types:
required
optional
required but mutually exclusive ->
a. ex. lets say I've 3 params: (param1), (param2, param3). If I have param1, I cannot set param2 or param3. If I can set both param2 and param3, but I cannot set param1
optional but mutually exclusive -> same logic as above but these are optional params. That is optionally I can set either param1 or (param2 and param3).
I figured how to get required and optional cases, but cannot get case 3 and 4.
Any ideas how to proceed.
These checks can be done at runtime, but I want these set of rules being implemented at compile time
case class Person(name: String, /*required*/
address: String, /*optional*/
city: String, /* reqd exclusive*/
county: String, /* reqd exclusive*/
state: String /* reqd exclusive*/,
ssn: String, /* optional exclusive*/
insurance: String, /* opt exclusive*/
passport: String /* opt exclusive*/)
// where (city) and (county, state) are required but are mutually exclusive
// (ssn) and (insurance, passport) are optional but are mutually exclusive.
// If I set passport, I've to set insurance
sealed trait PersonInfo
object PersonInfo {
sealed trait Empty extends PersonInfo
sealed trait Name extends PersonInfo
sealed trait Address extends PersonInfo
type Required = Empty with Name with Address
}
case class PersonBuilder[T <: PersonInfo]
(name: String = "", address: String = "", city: String = "", county: String = "",
state: String = "", ssn: String = "", insurance: String = "",passport: String ="") {
def withName(name: String): PersonBuilder[T with PersonInfo.Name] =
this.copy(name = name)
def withTask(address: String): PersonBuilder[T with PersonInfo.Address ] =
this.copy(address = address)
def withCity(city: String): PersonBuilder[T] =
this.copy(city = city)
def withCountry(county: String): PersonBuilder[T] =
this.copy(county = county)
def withState(state: String): PersonBuilder[T] =
this.copy(state = state)
def withSsn(ssn: String): PersonBuilder[T] =
this.copy(ssn = ssn)
def withInsurance(insurance: String): PersonBuilder[T] =
this.copy(insurance = insurance)
def withPassport(passport: String): PersonBuilder[T] =
this.copy(passport = passport)
def build(implicit ev: T =:= PersonInfo.Required): Person =
Person(name, address, city, county, state, ssn, insurance, passport)
}
here's the build
val testPerson = PersonBuilder[PersonInfo.Empty]()
.withName("foo")
.withSsn("bar")
As mentioned in a comment, if creating a builder is not a hard requirement, a viable choice could be to make those requirements explicit in the types, using sum types for exclusive choices and Options for optional ones, as in the following example:
sealed abstract class Location extends Product with Serializable {
def value: String
}
object Location {
final case class City(value: String) extends Location
final case class County(value: String) extends Location
final case class State(value: String) extends Location
}
sealed abstract class Identity extends Product with Serializable {
def value: String
}
object Identity {
final case class Ssn(value: String) extends Identity
final case class Insurance(value: String) extends Identity
final case class Passport(value: String) extends Identity
}
final case class Person(
name: String,
address: Option[String],
location: Location,
identity: Option[Identity],
)
Scala 3 further introduced enums which makes the definition more compact and readable:
enum Location(value: String) {
case City(value: String) extends Location(value)
case County(value: String) extends Location(value)
case State(value: String) extends Location(value)
}
enum Identity(value: String) {
case Ssn(value: String) extends Identity(value)
case Insurance(value: String) extends Identity(value)
case Passport(value: String) extends Identity(value)
}
final case class Person(
name: String,
address: Option[String],
location: Location,
identity: Option[Identity],
)
And making Options default to None you get a very similar experience to custom-made builders without any additional code:
final case class Person(
name: String,
location: Location,
address: Option[String] = None,
identity: Option[Identity] = None,
)
Person("Alice", Location.City("New York"))
.copy(identity = Some(Identity.Ssn("123456")))
Which you can further refine very easily:
final case class Person(
name: String,
location: Location,
address: Option[String] = None,
identity: Option[Identity] = None
) {
def withAddress(address: String): Person =
this.copy(address = Some(address))
def withIdentity(identity: Identity): Person =
this.copy(identity = Some(identity))
}
Person("Alice", Location.City("New York")).withIdentity(Identity.Ssn("123456"))
You can play around with this code here on Scastie.

Get the property of parent class in case classes

If I have a case class that inherits another class like this
class Person(name: String) {
}
case class Male() extends Person("Jack") {
def f = super.name // Doesn't work
}
How to get the name property from Male class ?
class Person(name: String) {
In this declaration, name is not a field of the class, it is just a constructor parameter. So it is accessible inside the constructor, but not outside (including in a subclass). You can make it a field by making it a val:
class Person(val name: String) {
Confusingly, constructor parameters for a case class are also fields even without val
Try to make name a val (or at least protected val) in Person, then it will be accessible in Male
class Person(val name: String)
case class Male() extends Person("Jack") {
def f = name
}
When you wrote class Person(name: String) you actually created class Person(private[this] val name: String) so name was not accessible in Male.
https://stackoverflow.com/a/62686327/5249621

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

Ducktyping implicit conversion in Scala

Is it possible to have a duck-typing style conversion where a converter will accept an arbitrary object and will try to convert it to the destination type?
case class RequiredFields(name: String, surname: String)
case class Person(name: String, surname: String, age: Option[Int])
implicit def arbitraryToPerson(object: ????): Person = Person(object.name, object.surname, None)
Arbitrary object doesn't have to be related to RequriedField class in any way, just the fields must exist.
I think you can combine structural typing and implicit conversions. Like so:
type RequiredFields = {
def name: String
def surname: String
}
case class NotAPerson(name: String, surname: String)
case class Person(name: String, surname: String, age: Option[Int])
implicit def arbitraryToPerson(o: RequiredFields): Person = Person(o.name, o.surname, None)
val p:Person = NotAPerson("oh", "my")
Are you sure that's a good idea, though? I think the much better pattern here would be to define a type class with the required fields. Have you considered that?
As hasumedic answered you probably want to use a trait.
If for some reason you don't want to use a trait (you don't have control over the package objects that are being sent to you etc) you can also use reflection to get the fields. (I don't actually know if this is a good idea, but my best guess is that it isn't)
case class RequiredFields(name: String, surname: String)
val rF = new RequriedFields("Hey", "yo")
val method = rF.getClass.getDeclaredMethod("name")
Then you get your value by invoking the getter for that field that scala makes for you
scala> val x = method.invoke(rF).asInstanceOf[String]
x: String = Hey
I think that you're looking for a trait:
trait Nameable {
def name: String
def surname: String
}
case class Person(name: String, surname: String, age: Option[Int]) extends Nameable
implicit def arbitraryToPerson(variable: Nameable): Person = Person(variable.name, variable.surname, None)
You can think of a trait like an interface. A characteristic of Scala traits is that they also accept implementations of its methods.
Classes extending this trait will need to implement the methods name and surname. In this case, the only valid Nameable parameter would be a Person. Having more classes that extend the trait would allow for some sort of polymorphism, making this implicit actually useful.
EDIT
From your comment and negative, just to point out that you can call your traits however you like. In your case, something like Nameable could work. It doesn't mean that there's a hierarchy, but that your object fulfils certain conditions, such as implementing those methods. Classes can extend more than one trait in Scala, using the with keyword.
case class Dog(name: String, surname: String) extends Animal with Nameable
In this case, your dog would be an animal, but you can enforce the implementation of the Nameable methods by extending the trait.
It seems you can do ducktyping driven conversion in the following way:
implicit def convertToPerson(obj: {def name(value: String): String; def surname(): String}): Person = {
Person(obj.name, obj.surname, None)
}

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