case class Employee(name :String, dept :String, empId:String, startDate:String)
val emp1 = Employee("abc","accounts", "1234","2019-09-28")
val emp2 = Employee("def","HR", "12345","2019-09-29")
val emp3 = Employee("pp","HR", "12346", "2019-09-28")
val emp4 = Employee("xyz","accounts", "12347","2019-09-20")
val list = Seq(emp1, emp2, emp3, emp4)
def isValidDate(date: LocalDateTime, e:
Employee):Boolean={
date.isAfter(e.startDate)
}
private def test(name :String, date:String,list:Seq[Employee])={
list.exists(e =>isValidDate(LocalDate.parse(date).atStartOfDay(), e))
}
test("abc",2019-09-28, list)
test("xyz",2019-09-28, list)
test("def",2019-09-28, list)
The above works but when I change method to below
def isValidDate(date: LocalDateTime, e:
Employee):Try[Boolean]={
date.isAfter(e.startDate)
}
The below line gives compile error as it expects a boolean
list.exists(e =>isValidDate(date, e))
I want the handling of success and failure be outside isValidDate method
First of all, you'll need to change the implementation of isValidDate:
def isValidDate(date: LocalDateTime, e: Employee): Try[Boolean]={
Try { date.isAfter(e.startDate) }
}
Then, the implementation of your higher order function would need to deal with the Try:
private def test(name: String, date: LocalDateTime, list: Seq[Employee]) = {
list.exists(isValidDate(date, _) match {
case Failure(_) => false
case Success(value: Boolean) => value
})
}
Or as Zang proposes in the comments:
private def test(name: String, date: LocalDateTime, list: Seq[Employee]) = {
list.exists(isValidDate(date, _).getOrElse(false))
}
I would say though, that I'm not sure all this is necessary. Does isAfter throw any exception? Try is mostly used to capture exceptional code, which I don't think is the case here.
If you want to handle the exception outside isValidDate then there is no point in changing isValidDate, you can simply catch the exception outside. For example
private def test(name: String, date: LocalDateTime, list: Seq[Employee]) =
Try {
list.exists(e => isValidDate(date, e))
}.getOrElse(false)
This will fail the whole test if there is an exception. If you want to ignore exceptions and keep testing later employees, do this
private def test(name: String, date: LocalDateTime, list: Seq[Employee]) =
list.exists(e => Try(isValidDate(date, e)).getOrElse(false))
However in this case I believe that it better to put the exception handling in isValidDate, even though this is not what you say you want:
def isValidDate(date: LocalDateTime, e: Employee): Boolean =
Try(date.isAfter(e.startDate)).getOrElse(false)
private def test(name: String, date: LocalDateTime, list: Seq[Employee]) =
list.exists(e => isValidDate(date, e))
Related
I have 2 case classes:
case class Person(fname: String, lname: String, age: Int, address: String)
case class PersonUpdate(fname: String, lname: String, age: Int)
so PersonUpdate dosent have all the fields Person have, and I want to write effective that get Person and PersonUpdate and find the fields that have different values:
for example:
def findChangedFields(person: Person, personUpdate: PersonUpdate): Seq[String] = {
var listOfChangedFields: List[String] = List.empty
if (person.fname == personUpdate.fname)
listOfChangedFields = listOfChangedFields :+ "fname"
if (person.lname == personUpdate.lname)
listOfChangedFields = listOfChangedFields :+ "lname"
if (person.age == personUpdate.age)
listOfChangedFields = listOfChangedFields :+ "age"
listOfChangedFields
}
findChangedFields(per, perUpdate)
but this is very ugly, how can I write this nicely with the magic of scala?
Something like this, maybe?
val fields = Seq("fname", "lname", "age")
val changedFields = person.productIterator
.zip(personUpdate.productIterator)
.zip(fields.iterator)
.collect { case ((a, b), name) if a != b => name }
.toList
Something like this:
case class Person(fname: String, lname: String, age: Int, address: String)
case class PersonUpdate(fname: String, lname: String, age: Int)
def findFirstNameChanged(person: Person, personUpdate: PersonUpdate): List[String] =
{
if (person.fname == personUpdate.fname) List("fname")
else Nil
}
def findLastNameChanged(person: Person, personUpdate: PersonUpdate): List[String] = {
if (person.lname == personUpdate.lname) List("lname")
else Nil
}
def findAgeNameChanged(person: Person, personUpdate: PersonUpdate): List[String] = {
if (person.age == personUpdate.age) List("age")
else Nil
}
def findChangedFields(person: Person, personUpdate: PersonUpdate): Seq[String] = {
findFirstNameChanged(person,personUpdate):::
findLastNameChanged(person,personUpdate) ::: findAgeNameChanged(person,personUpdate)
}
val per = Person("Pedro","Luis",22,"street")
val personUpdate = PersonUpdate("Pedro", "Luis",27)
findChangedFields(per, personUpdate)
I think your problem is similar to compare two Set of tuple. Please feel free two correct me.
So here is my solution which will work for any two case class having field names in any order
def caseClassToSet[T](inp: T)(implicit ct: ClassTag[T]): Set[(String, AnyRef)] = {
ct.runtimeClass.getDeclaredFields.map(f => {
f.setAccessible(true)
val res = (f.getName, f.get(inp))
f.setAccessible(false)
res
}).toSet
}
val person = Person("x", "y", 10, "xy")
val personUpdate = PersonUpdate("z","y",12)
val personParams: Set[(String, AnyRef)] = caseClassToSet(person)
val personUpdateParams: Set[(String, AnyRef)] = caseClassToSet(personUpdate)
println(personUpdateParams diff personParams)
Got help from Get field names list from case class
I have the following 3 case classes:
case class Profile(name: String,
age: Int,
bankInfoData: BankInfoData,
userUpdatedFields: Option[UserUpdatedFields])
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType)
this is just enums, but i added anyway:
sealed trait AccountType extends EnumEntry
object AccountType extends Enum[AccountType] {
val values: IndexedSeq[AccountType] = findValues
case object Personal extends AccountType
case object Business extends AccountType
}
my task is - i need to write a funcc Profile and compare UserUpdatedFields(all of the fields) with SOME of the fields in BankInfoData...this func is to find which fields where updated.
so I wrote this func:
def findDiff(profile: Profile): Seq[String] = {
var listOfFieldsThatChanged: List[String] = List.empty
if (profile.bankInfoData.contactPerson != profile.userUpdatedFields.get.contactPerson){
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "contactPerson"
}
if (profile.bankInfoData.phoneNumber != profile.userUpdatedFields.get.phoneNumber) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "phoneNumber"
}
if (profile.bankInfoData.accountType != profile.userUpdatedFields.get.accountType) {
listOfFieldsThatChanged = listOfFieldsThatChanged :+ "accountType"
}
listOfFieldsThatChanged
}
val profile =
Profile(
"nir",
34,
BankInfoData(1, "somewhere", 2, "john", 123, AccountType.Personal),
Some(UserUpdatedFields("lee", 321, AccountType.Personal))
)
findDiff(profile)
it works, but wanted something cleaner..any suggestions?
Each case class extends Product interface so we could use it to convert case classes into sets of (field, value) elements. Then we can use set operations to find the difference. For example,
def findDiff(profile: Profile): Seq[String] = {
val userUpdatedFields = profile.userUpdatedFields.get
val bankInfoData = profile.bankInfoData
val updatedFieldsMap = userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator).toMap
val bankInfoDataMap = bankInfoData.productElementNames.zip(bankInfoData.productIterator).toMap
val bankInfoDataSubsetMap = bankInfoDataMap.view.filterKeys(userUpdatedFieldsMap.keys.toList.contains)
(bankInfoDataSubsetMap.toSet diff updatedFieldsMap.toSet).toList.map { case (field, value) => field }
}
Now findDiff(profile) should output List(phoneNumber, contactPerson). Note we are using productElementNames from Scala 2.13 to get the filed names which we then zip with corresponding values
userUpdatedFields.productElementNames.zip(userUpdatedFields.productIterator)
Also we rely on filterKeys and diff.
A simple improvement would be to introduce a trait
trait Fields {
val contactPerson: String
val phoneNumber: Int
val accountType: AccountType
def findDiff(that: Fields): Seq[String] = Seq(
Some(contactPerson).filter(_ != that.contactPerson).map(_ => "contactPerson"),
Some(phoneNumber).filter(_ != that.phoneNumber).map(_ => "phoneNumber"),
Some(accountType).filter(_ != that.accountType).map(_ => "accountType")
).flatten
}
case class BankInfoData(accountNumber: Int,
bankAddress: String,
bankNumber: Int,
contactPerson: String,
phoneNumber: Int,
accountType: String) extends Fields
case class UserUpdatedFields(contactPerson: String,
phoneNumber: Int,
accountType: AccountType) extends Fields
so it was possible to call
BankInfoData(...). findDiff(UserUpdatedFields(...))
If you want to further-improve and avoid naming all the fields multiple times, for example shapeless could be used to do it compile time. Not exactly the same but something like this to get started. Or use reflection to do it runtime like this answer.
That would be a very easy task to achieve if it would be an easy way to convert case class to map. Unfortunately, case classes don't offer that functionality out-of-box yet in Scala 2.12 (as Mario have mentioned it will be easy to achieve in Scala 2.13).
There's a library called shapeless, that offers some generic programming utilities. For example, we could write an extension function toMap using Record and ToMap from shapeless:
object Mappable {
implicit class RichCaseClass[X](val x: X) extends AnyVal {
import shapeless._
import ops.record._
def toMap[L <: HList](
implicit gen: LabelledGeneric.Aux[X, L],
toMap: ToMap[L]
): Map[String, Any] =
toMap(gen.to(x)).map{
case (k: Symbol, v) => k.name -> v
}
}
}
Then we could use it for findDiff:
def findDiff(profile: Profile): Seq[String] = {
import Mappable._
profile match {
case Profile(_, _, bankInfo, Some(userUpdatedFields)) =>
val bankInfoMap = bankInfo.toMap
userUpdatedFields.toMap.toList.flatMap{
case (k, v) if bankInfoMap.get(k).exists(_ != v) => Some(k)
case _ => None
}
case _ => Seq()
}
}
Let's say i have 2 cases classes:
case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
is there a nice way to "translate" a list of strings to class Human? smth like:
def superMethod[A](params: List[String]): A = ???
val params: List[Any] = List("john", 100, "dollar")
superMethod(params) // => Human("john", Money(100, "dollar"))
so essentially i know type A only in runtime
UPDATE: i found ~ what i was looking for. it seems i can do it via shapeless. example i found in github
Here is an implementation that works for generic classes A.
It relies on runtime reflection (that is, a different TypeTag can be passed to the method at runtime). The following obvious conditions must be fulfilled in order to use this method:
A must be on the class path, or otherwise be loadable by the used class loader
TypeTag must be available for A at the call site.
The actual implementation is in the Deserializer object. Then comes a little demo.
The deserializer:
import scala.reflect.runtime.universe.{TypeTag, Type}
object Deserializer {
/** Extracts an instance of type `A` from the
* flattened `Any` constructor arguments, and returns
* the constructed instance together with the remaining
* unused arguments.
*/
private def deserializeRecHelper(
flattened: List[Any],
tpe: Type
): (Any, List[Any]) = {
import scala.reflect.runtime.{universe => ru}
// println("Trying to deserialize " + tpe + " from " + flattened)
// println("Constructor alternatives: ")
// val constructorAlternatives = tpe.
// member(ru.termNames.CONSTRUCTOR).
// asTerm.
// alternatives.foreach(println)
val consSymb = tpe.
member(ru.termNames.CONSTRUCTOR).
asTerm.
alternatives(0).
asMethod
val argsTypes: List[Type] = consSymb.paramLists(0).map(_.typeSignature)
if (tpe =:= ru.typeOf[String] || argsTypes.isEmpty) {
val h :: t = flattened
(h, t)
} else {
val args_rems: List[(Any, List[Any])] = argsTypes.scanLeft(
(("throwaway-sentinel-in-deserializeRecHelper": Any), flattened)
) {
case ((_, remFs), t) =>
deserializeRecHelper(remFs, t)
}.tail
val remaining: List[Any] = args_rems.last._2
val args: List[Any] = args_rems.unzip._1
val runtimeMirror = ru.runtimeMirror(getClass.getClassLoader)
val classMirror = runtimeMirror.reflectClass(tpe.typeSymbol.asClass)
val cons = classMirror.reflectConstructor(consSymb)
// println("Build constructor arguments array for " + tpe + " : " + args)
val obj = cons.apply(args:_*)
(obj, remaining)
}
}
def deserialize[A: TypeTag](flattened: List[Any]): A = {
val (a, rem) = deserializeRecHelper(
flattened,
(implicitly: TypeTag[A]).tpe
)
require(
rem.isEmpty,
"Superfluous arguments remained after deserialization: " + rem
)
a.asInstanceOf[A]
}
}
Demo:
case class Person(id: String, money: Money, pet: Pet, lifeMotto: String)
case class Money(num: Int, currency: String)
case class Pet(color: String, species: Species)
case class Species(description: String, name: String)
object Example {
def main(args: Array[String]): Unit = {
val data = List("Bob", 42, "USD", "pink", "invisible", "unicorn", "what's going on ey?")
val p = Deserializer.deserialize[Person](data)
println(p)
}
}
Output:
Person(Bob,Money(42,USD),Pet(pink,Species(invisible,unicorn)),what's going on ey?)
Discussion
This implementation is not restricted to case classes, but it requires each "Tree-node-like" class to have exactly one constructor that accepts either
primitive types (Int, Float), or
strings, or
other "Tree-node-like" classes.
Note that the task is somewhat ill-posed: what does it mean to say that all constructor arguments are flattened in a single list? Given the class Person(name: String, age: Int), will the List[Any] contain every single byte of the name as a separate entry? Probably not. Therefore, strings are handled by the deserializer in a special way, and all other collection-like entities are not supported for the same reasons (unclear where to stop parsing, because size of the collection is not known).
In case A is not a generic type, but effectively Human, you can use a companion object to the case class Human:
object Human {
def fromList(list: List[String]): Human = list match {
case List(name, amount, currency) => Human(name, Money(amount.toInt, currency))
case _ => handle corner case
}
}
Which you can call:
Human.fromList(List("john", "100", "dollar"))
To make it safe, don't forget to handle the case of lists whose size wouldn't be 3; and of lists whose 2nd element can't be cast to an Int:
import scala.util.Try
object Human {
def fromList(list: List[String]): Option[Human] = list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount.toInt, currency))).toOption
case _ => None
}
}
Edit: Based on your last comment, you might find this usefull:
case class Money(amount: Int, currency: String)
case class Human(name: String, money: Money)
case class SomethingElse(whatever: Double)
object Mapper {
def superMethod(list: List[String]): Option[Any] =
list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount.toInt, currency))).toOption
case List(whatever) => Try(SomethingElse(whatever.toDouble)).toOption
case _ => None
}
}
println(Mapper.superMethod(List("john", 100, "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod(List(17d)))
> Some(SomethingElse(17.0))
or alternatively:
object Mapper {
def superMethod[A](list: List[String]): Option[A] =
(list match {
case List(name, amount, currency) =>
Try(Human(name, Money(amount, currency))).toOption
case List(whatever) =>
Try(SomethingElse(whatever.toDouble)).toOption
case _ => None
}).map(_.asInstanceOf[A])
}
println(Mapper.superMethod[Human](List("john", "100", "dollar")))
> Some(Human(john,Money(100,dollar)))
println(Mapper.superMethod[SomethingElse](List("17.2")))
> Some(SomethingElse(17.0))
I am trying to understand the scala unapply method.
Below is my understanding. Say if I have a Person object:
class Person(val fname: String, val lname: String)
object Person{
def unapply(x: Person) : Option[(String, String)] =
Some(x.fname,x.lname)
}
new Person("Magic", "Mike") match {
case Person(x, y) => s"Last Name is ${y}"
case _ => "Unknown"
}
This i presume the case calls something like:
val temp = Person.unapply(new Person("Magic", "Mike"))
if (temp != None) { val (x, y) = temp.get }
else { <go to next case> }
but how does below unapply work when i have like below:
new Person("Magic", "Mike") match {
case Person("Harold", y) => s"Last Name is ${y}"
case Person("Magic", y) => s"Last Name is ${y}"
case _ => "Unknown"
}
How does it access the value of fname("Magic") in unapply method and give me the same/correct result as the first one?
Running scalac with -Xprint:patmat will show you how syntactic trees look after pattern matching phase:
scalac -Xprint:patmat test.scala
case <synthetic> val x1: Person = new Person("Magic", "Mike");
case10(){
<synthetic> val o12: Option[(String, String)] = Person.unapply(x1);
if (o12.isEmpty.unary_!)
{
<synthetic> val p3: String = o12.get._1;
val y: String = o12.get._2;
if ("Harold".==(p3))
matchEnd9(scala.StringContext.apply("Last Name is ", "").s(y))
else
case11()
}
else
case11()
};
case11(){
<synthetic> val o14: Option[(String, String)] = Person.unapply(x1);
if (o14.isEmpty.unary_!)
{
<synthetic> val p5: String = o14.get._1;
val y: String = o14.get._2;
if ("Magic".==(p5))
matchEnd9(scala.StringContext.apply("Last Name is ", "").s(y))
else
case13()
}
else
case13()
};
case13(){
matchEnd9("Unknown")
};
As you can see, for each case first it calls unapply on the matched object, then if Option is not empty (so it has matched), it checks if one of the elements of tuple is equal to expected value, if so then it matches to the closure for this case.
I am trying to parse a csv row here and each field can be a different type. To handle the error accumulation I am using Either[String, B] where the String is an error message and B is the value. The issue here is that B can be different types, Option[Int], String, Array[String], resulting in my Map being type (String, Either[String,java.io.Serializable]) effectively making the Map unreusable. Is there a way (I'm definitely sure there is) to more elegantly accumulate errors while also reusing those values to populate properties on an object?
override def parseCsv(implicit fields: Map[String, String]): Either[String, User] = {
val parsedValues = Map(Headers.Id -> getFieldAsString(Headers.Id),
Headers.FirstName -> getFieldAsString(Headers.FirstName),
Headers.LastName -> getFieldAsString(Headers.LastName),
Headers.Email -> getFieldAsString(Headers.Email),
Headers.TimeZone -> getFieldAsString(Headers.TimeZone),
Headers.Region -> getOption(Headers.Region),
Headers.Phone -> getOption(Headers.Phone),
Headers.ProfileImage -> getFieldAsString(Headers.ProfileImage),
Headers.Roles -> getFieldAsArray(Headers.Roles))
val errors = parsedValues.collect { case (key, Left(errors)) => errors }
if (!errors.isEmpty) Left(errors.mkString(", "))
else {
val user = new User
user.id = getFieldAsString(Headers.Id).right.get
user.firstName = getFieldAsString(Headers.FirstName).right.get
user.lastName = getFieldAsString(Headers.LastName).right.get
user.email = getFieldAsString(Headers.Email).right.get
user.timeZone = getFieldAsString(Headers.TimeZone).right.get
user.phoneNumber = (for {
region <- getOption(Headers.Region).right.get
phone <- getOption(Headers.Phone).right.get
_ = validatePhoneNumber(phone, region)
} yield {
new PhoneNumber(region, phone)
}).orNull
user.profileImageUrl = getFieldAsString(Headers.ProfileImage).right.get
user.roles = getFieldAsArray(Headers.Roles).right.get
Right(user)
}
}
Create case classes for all types of Bs. These case classes must extend some common trait. While populating the user object just pattern match and retrieve values.
sealed trait Result {
val paramName: String
}
case class OptionInt(override val paramName: String, value: Option[Int]) extends Result
case class ArrayString(override val paramName: String, value: Array[String]) extends Result
case class StringValue(override val paramName: String, value: String) extends Result
now the final type would be like Either[String, Result]
after parsing the whole file create a List[Result]
If you are expecting age as Option[Int] and firstName as String then do this
list.foreach { result =>
result match {
case Option("age", value) => userId.age = value.getOrElse(defaultAge)
case StringValue("firstName", value) => userId.firstName = value
case StringValue("email", value) => userId.email = value
case _ => //do nothing
}
}