Case Classes with optional fields in Scala - scala

For example, I have this case class:
case class Student (firstName : String, lastName : String)
If I use this case class, is it possible that supplying data to the fields inside the case class are optional? For example, I'll do this:
val student = new Student(firstName = "Foo")
Thanks!

If you just want to miss the second parameter without a default information, I suggest you to use an Option.
case class Student(firstName: String, lastName: Option[String] = None)
Now you might create instances this way:
Student("Foo")
Student("Foo", None) // equal to the one above
Student("Foo", Some("Bar")) // neccesary to add a lastName
To make it usable as you wanted it, I will add an implicit:
object Student {
implicit def string2Option(s: String) = Some(s)
}
Now you are able to call it those ways:
import Student._
Student("Foo")
Student("Foo", None)
Student("Foo", Some("Bar"))
Student("Foo", "Bar")

You were close:
case class Student (firstName : String = "John", lastName : String = "Doe")
val student = Student(firstName = "Foo")
Another possibility is partially applied function:
case class Student (firstName : String, lastName : String)
val someJohn = Student("John", _: String)
//someJohn: String => Student = <function1>
val johnDoe = someJohn("Doe")
//johnDoe: Student = Student(John,Doe)
And to be complete, you can create some default object and then change some field:
val johnDeere = johnDoe.copy(lastName="Deere")
//johnDeer: Student = Student(John,Deere)

I would see two ways this is normally done.
1. default parameters
case class Student (firstName : String, lastName : String = "")
Student("jeypijeypi") # Student(jeypijeypi,)
2. alternative constructors
case class Student (firstName : String, lastName : String)
object Student {
def apply(firstName: String) = new Student(firstName,"")
}
Student("jeypijeypi") # Student(jeypijeypi,)
Which one is better depends slightly on the circumstances. The latter gives you more freedom: you can make any parameter(s) optional, or even change their order (not recommended). Default parameters need always to be at the end of the parameter list, I think. You can also combine these two ways.
Note: within the alternative constructors you need new to point the compiler to the actual constructor. Normally new is not used with case classes.

Related

Scala Option if exists then set multiple vals if None set same multiple vals to empty string

I have a Option employee object. From employee I want to get the name, department, address, number, age or anything else from it if it exists but if None the name, department, and everything else I want to set to "".
I would like to just do like in Java:
if (employee.isDefined) {
val name = employee.get.getEmployeName
val department = employee.get.getDepartment
val address = employee.get.getAddress
val number = employee.get.getNumber
val age = employee.get.getAge
} else {
val name, department, address, number, age = ""
}
but I learned it does not work like that. It looks like I would need another employee object and set the values like and then access it later:
if (employee.isDefined) {
emp.setName(employee.get.getEmployeName)
emp.setDepartment(employee.get.getDepartment)
...
} else {
emp.setName("")
emp.setDepartment("")
...
}
I also experimented with tuples?
val employeeInfo = employee match {
case Some(emp) => (employee.getEmployeName, employee.getDepartment, employee.getAddress,
employee.getNumber, employee.getAge)
case None => ("", "", "", "", "")
}
val name = employeeInfo._1
val department = employeeInfo._2
val address = employeeInfo._3
...
Are these methods okay? Or are there any better ways to do this? Thanks for the help
.getOrElse() is the usual means of extracting a value from an Option while specifying a default if the option is None.
In your case, however, it is the container of many values that might be None. For that I'd recommend .fold().
case class Employee(empName : String
,dept : String
,addr : String
,num : String
,age : String)
val employee: Option[Employee] =
Some(Employee("Jo","mkt","21A","55","44"))
//or None
val name = employee.fold("")(_.empName)
val department = employee.fold("")(_.dept)
val address = employee.fold("")(_.addr)
val number = employee.fold("")(_.num)
val age = employee.fold("")(_.age)
But I have to agree with the comments from #sinanspd, your overall design is questionable at best.
This is how I would tackle this specific operation:
val (name, department, address, number, age) =
employee.fold(("", "", "", "", "")) { e =>
(e.getEmployeName, e.getDepartment, e.getAddress, e.getNumber, e.getAge)
}
But as suggested in the comments, it is worth looking at the overall design. For example it may be better to keep the values optional:
val employeeData: Option[(String, String, String, String, String)] =
employee.map{ e =>
(e.getEmployeName, e.getDepartment, e.getAddress, e.getNumber, e.getAge)
}
This allows you to tell whether a value is "" because employee was None or because the value in the Employee object was "". And you would probably define a different class to represent this restricted set of employee data to make the code cleaner and clearer.

Difference between Option[String] = None and Option[String] in scala

I have started learning Scala and going through the code (case classes and other stuff) in my project.
I see a case class defined like this:
case class Test(firstName: Option[String] = None, lastName: Option[String])
I have few questions on the above case class:
What is the difference between Option[String] = None and Option[String]?
What is the correct usage, when should we use Option[String] = None and Option[String]?
The "regex" for a parameter is something like this: name: type (= defaultValue) - Note that the default value part is optional.
Thus firstName: Option[String] = None means the parameter is named firstName and it is of type Option[String] and its default value is a None (also remember the Option type is used to explain that a value may exist or not. And that None means it doesn't exist).
Both are correct depending on the context, in this case, this class can be used like:
Test(lastName = None)
// res: Test = Test(None,None) - A person without names.
Test(lastName = Some("Mejia"))
// res: Test = Test(None,Some(Mejia)) - A person with just its last name, which it is "Mejia".
Test(firstName = Some("Luis"), lastName = Some("Mejia"))
// res: Test = Test(Some(Luis),Some(Mejia)) - A person whose first name is "Luis" and its last name is "Mejia".
Test(firstName = Some("Luis"), lastName = None)
// res: Test = Test(Some(Luis),None) - A person with just its first name, which it is "Luis".
Note that I always have to specify its last name because it doesn't have a default value.

How to print a Monocle Lens as a property accessor style string

Using Monocle I can define a Lens to read a case class member without issue,
val md5Lens = GenLens[Message](_.md5)
This can used to compare the value of md5 between two objects and fail with an error message that includes the field name when the values differ.
Is there a way to produce a user-friendly string from the Lens alone that identifies the field being read by the lens? I want to avoid providing the field name explicitly
val md5LensAndName = (GenLens[Message](_.md5), "md5")
If there is a solution that also works with lenses with more than one component then even better. For me it would be good even if the solution only worked to a depth of one.
This is fundamentally impossible. Conceptually, lens is nothing more than a pair of functions: one to get a value from object and one to obtain new object using a given value. That functions can be implemented by the means of accessing the source object's fields or not. In fact, even GenLens macro can use a chain field accessors like _.field1.field2 to generate composite lenses to the fields of nested objects. That can be confusing at first, but this feature have its uses. For example, you can decouple the format of data storage and representation:
import monocle._
case class Person private(value: String) {
import Person._
private def replace(
array: Array[String], index: Int, item: String
): Array[String] = {
val copy = Array.ofDim[String](array.length)
array.copyToArray(copy)
copy(index) = item
copy
}
def replaceItem(index: Int, item: String): Person = {
val array = value.split(delimiter)
val newArray = replace(array, index, item)
val newValue = newArray.mkString(delimiter)
Person(newValue)
}
def getItem(index: Int): String = {
val array = value.split(delimiter)
array(index)
}
}
object Person {
private val delimiter: String = ";"
val nameIndex: Int = 0
val cityIndex: Int = 1
def apply(name: String, address: String): Person =
Person(Array(name, address).mkString(delimiter))
}
val name: Lens[Person, String] =
Lens[Person, String](
_.getItem(Person.nameIndex)
)(
name => person => person.replaceItem(Person.nameIndex, name)
)
val city: Lens[Person, String] =
Lens[Person, String](
_.getItem(Person.cityIndex)
)(
city => person => person.replaceItem(Person.cityIndex, city)
)
val person = Person("John", "London")
val personAfterMove = city.set("New York")(person)
println(name.get(personAfterMove)) // John
println(city.get(personAfterMove)) // New York
While not very performant, that example illustrates the idea: Person class don't have city or address fields, but by wrapping data extractor and a string rebuild function into Lens, we can pretend it have them. For more complex objects, lens composition works as usual: inner lens just operates on extracted object, relying on outer one to pack it back.

How to modify this nested case classes with "Seq" fields?

Some nested case classes and the field addresses is a Seq[Address]:
// ... means other fields
case class Street(name: String, ...)
case class Address(street: Street, ...)
case class Company(addresses: Seq[Address], ...)
case class Employee(company: Company, ...)
I have an employee:
val employee = Employee(Company(Seq(
Address(Street("aaa street")),
Address(Street("bbb street")),
Address(Street("bpp street")))))
It has 3 addresses.
And I want to capitalize the streets start with "b" only. My code is mess like following:
val modified = employee.copy(company = employee.company.copy(addresses =
employee.company.addresses.map { address =>
address.copy(street = address.street.copy(name = {
if (address.street.name.startsWith("b")) {
address.street.name.capitalize
} else {
address.street.name
}
}))
}))
The modified employee is then:
Employee(Company(List(
Address(Street(aaa street)),
Address(Street(Bbb street)),
Address(Street(Bpp street)))))
I'm looking for a way to improve it, and can't find one. Even tried Monocle, but can't apply it to this problem.
Is there any way to make it better?
PS: there are two key requirements:
use only immutable data
don't lose other existing fields
As Peter Neyens points out, Shapeless's SYB works really nicely here, but it will modify all Street values in the tree, which may not always be what you want. If you need more control over the path, Monocle can help:
import monocle.Traversal
import monocle.function.all._, monocle.macros._, monocle.std.list._
val employeeStreetNameLens: Traversal[Employee, String] =
GenLens[Employee](_.company).composeTraversal(
GenLens[Company](_.addresses)
.composeTraversal(each)
.composeLens(GenLens[Address](_.street))
.composeLens(GenLens[Street](_.name))
)
val capitalizer = employeeStreeNameLens.modify {
case s if s.startsWith("b") => s.capitalize
case s => s
}
As Julien Truffaut points out in an edit, you can make this even more concise (but less general) by creating a lens all the way to the first character of the street name:
import monocle.std.string._
val employeeStreetNameFirstLens: Traversal[Employee, Char] =
GenLens[Employee](_.company.addresses)
.composeTraversal(each)
.composeLens(GenLens[Address](_.street.name))
.composeOptional(headOption)
val capitalizer = employeeStreetNameFirstLens.modify {
case 'b' => 'B'
case s => s
}
There are symbolic operators that would make the definitions above a little more concise, but I prefer the non-symbolic versions.
And then (with the result reformatted for clarity):
scala> capitalizer(employee)
res3: Employee = Employee(
Company(
List(
Address(Street(aaa street)),
Address(Street(Bbb street)),
Address(Street(Bpp street))
)
)
)
Note that as in the Shapeless answer, you'll need to change your Employee definition to use List instead of Seq, or if you don't want to change your model, you could build that transformation into the Lens with an Iso[Seq[A], List[A]].
If you are open to replacing the addresses in Company from Seq to List, you can use "Scrap Your Boilerplate" from shapeless (example).
import shapeless._, poly._
case class Street(name: String)
case class Address(street: Street)
case class Company(addresses: List[Address])
case class Employee(company: Company)
val employee = Employee(Company(List(
Address(Street("aaa street")),
Address(Street("bbb street")),
Address(Street("bpp street")))))
You can create a polymorphic function which capitalizes the name of a Street if the name starts with a "b".
object capitalizeStreet extends ->(
(s: Street) => {
val name = if (s.name.startsWith("b")) s.name.capitalize else s.name
Street(name)
}
)
Which you can use as :
val afterCapitalize = everywhere(capitalizeStreet)(employee)
// Employee(Company(List(
// Address(Street(aaa street)),
// Address(Street(Bbb street)),
// Address(Street(Bpp street)))))
Take a look at quicklens
You could do it like this
import com.softwaremill.quicklens._
case class Street(name: String)
case class Address(street: Street)
case class Company(address: Seq[Address])
case class Employee(company: Company)
object Foo {
def foo(e: Employee) = {
modify(e)(_.company.address.each.street.name).using {
case name if name.startsWith("b") => name.capitalize
case name => name
}
}
}

Scala local variable inside primary constructor

How in Scala I can define local variable in primary constructor?
I need to solve this exercise from Scala for the impatient book:
Write a class Person with a primary constructor that accepts a string
containing a first name, a space, and a last name, such as new
Person("Fred Smith"). Supply read-only properties firstName and
lastName. Should the primary constructor parameter be a var, a val, or
a plain parameter? Why?
And for now my solution looks like this:
class Person(firstLast: String) {
private[this] val firstLastAsArr = firstLast.trim.split(" ")
val firstName = firstLastAsArr (0)
val lastName = firstLastAsArr (1)
}
How I can restrict firstLastAsArr variable visibility to primary constructor scope (now it have class scope)?
One solution is to initialize firstName and lastName at once, thereby allowing to turn firstLastAsArr into a local temporary value inside your initialization block:
class Person(firstLast: String) {
val (firstName, lastName) = {
val firstLastAsArr = firstLast.trim.split(" ")
(firstLastAsArr(0), firstLastAsArr(1))
}
}
It is not a general answer, but in this particular way you may write:
val Array(firstName, lastName) = firstLast.trim.split(" ")
You don't strictly need the intermediate variable:
class Person(firstLast: String) {
val (firstName, lastName) =
firstLast.trim.split(" ") match {case Array(first, last) => (first, last)}
}
However, if your transformation from firstLast to firstName and lastName grows a big longer, for example, because you check that there is exactly one first and one last name, then I would encapsulate the whole splitting-business in a dedicated method:
class Person(firstLast: String) {
val (firstName, lastName) = split(firstLast)
private def split(firstLast: String): (String, String) = {
val firstLastAsArr = firstLast.trim.split(" ")
...
(first, last)
}
}
Pattern matching in constructor works just fine, but you should consider moving such logic from constructor to factory method:
case class Person(firstName: String, lastName: String)
object Person{
def apply(firstLast: String) = {
val firstLastAsArr = firstLast.trim.split(" ")
new Person(firstLastAsArr(0), firstLastAsArr(1))
}
}
val p = Person("My Title")
Pattern maching in primary constructor works well
class Person(_fullName:String) {
val (firstName, lastName) = _fullName.split(" ") match {
case Array(x:String, y:String, _*) => (x,y)
case _ => (null,null)
}
}
See my github for full answer
https://github.com/BasileDuPlessis/scala-for-the-impatient/blob/master/src/main/scala/com/basile/scala/ch05/Ex07.scala