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
Related
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.
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.
The scenario is similar to the question at How to better parse the same table twice with Anorm? however the described solutions on that question can no longer be used.
On the scenario where a Message has 2 users I need to parse the from_user and to_user with SQL joins.
case class User(id: Long, name: String)
case class Message(id: Long, body: String, to: User, from: User)
def userParser(alias: String): RowParser[User] = {
get[Long](alias + "_id") ~ get[String](alias + "_name") map {
case id~name => User(id, name)
}
}
val parser: RowParser[Message] = {
userParser("from_user") ~
userParser("to_user") ~
get[Long]("messages.id") ~
get[String]("messages.name") map {
case from~to~id~body => Message(id, body, to, from)
}
}
// More alias here possible ?
val aliaser: ColumnAliaser = ColumnAliaser.withPattern((0 to 2).toSet, "from_user.")
SQL"""
SELECT from_user.* , to_user.*, message.* FROM MESSAGE
JOIN USER from_user on from_user.id = message_from_user_id
JOIN USER to_user on to_user.id = message.to_user
"""
.asTry(parser, aliaser)
If I'm right thinking you want to apply multiple ColumnAliaser with different aliasing policies to the same query, it's important to understand that ColumnAliaser is "just" a specific implementation of Function[(Int, ColumnName), Option[String]], so it can be defined/composed as any Function, and is simplified by the factory functions in its companion object.
import anorm.{ ColumnAliaser, ColumnName }
val aliaser = new ColumnAliaser {
def as1 = ColumnAliaser.withPattern((0 to 2).toSet, "from_user.")
def as2 = ColumnAliaser.withPattern((2 to 4).toSet, "to_user.")
def apply(column: (Int, ColumnName)): Option[String] =
as1(column).orElse(as2(column))
}
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
}
}
}
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.