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.
Related
Say I have a method like this:
def getClassFromIterable(iterable: Iterable[Any]): Class[_] = {
iterable.head.getClass
}
This will get the class of the top of the list, but will fail if the list is empty.
How can I get the class of a passed list that has zero or more elements?
Supplementing Luis' answer with a reflective solution, consider
import scala.reflect.runtime.universe._
def toTable[A <: Product](ps: List[A])(implicit ev: TypeTag[A]) = {
val separator = "\t\t"
ps match {
case Nil =>
val header = typeOf[A].members.collect { case m: MethodSymbol if m.isCaseAccessor => m.name }.toList
header.mkString("", separator , "\n")
case head :: _ =>
val header = head.productElementNames.toList
val rows = ps.map(_.productIterator.mkString(separator))
header.mkString("", separator, "\n") + rows.mkString("\n")
}
}
which outputs
case class Point(x: Double, y: Double)
println(toTable(List(Point(1,2), Point(3,4))))
x y
1.0 2.0
3.0 4.0
Based on Get field names list from case class
For this kind of problems, please consider using a typeclass instead.
import scala.collection.immutable.ArraySeq
trait TableEncoder[T] {
def header: ArraySeq[String]
def asRow(t: T): ArraySeq[String]
}
object TableEncoder {
def toTable[T](data: IterableOnce[T])
(implicit encoder: TableEncoder[T]): ArraySeq[ArraySeq[String]] = {
val builder = ArraySeq.newBuilder[ArraySeq[String]]
builder.addOne(encoder.header)
builder.addAll(data.iterator.map(encoder.asRow))
builder.result()
}
}
Which you can use like this:
final case class Point(x: Int, y: Int)
object Point {
final implicit val PointTableEncoder: TableEncoder[Point] =
new TableEncoder[Point] {
override val header: ArraySeq[String] =
ArraySeq("x", "y")
override def asRow(point: Point): ArraySeq[String] =
ArraySeq(
point.x.toString,
point.y.toString
)
}
}
TableEncoder.toTable(List(Point(1, 2), Point(3, 3)))
// res: ArraySeq[ArraySeq[String]] = ArraySeq(
// ArraySeq("x", "y"),
// ArraySeq("1", "2"),
// ArraySeq("3", "3")
// )
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
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'm writing extractor object for functions expressions. Here is how it looks like:
object FunctionTemplate2 {
private final val pattern = Pattern.compile("^(.+?)\\((.+?)\\,(.+?)\\)")
//e.g. foo(1, "str_arg")
def unapply(functionCallExpression: String): Option[(String, String, String)] = {
//parse expression and extract
}
}
And I can extract as follows:
"foo(1, \"str_arg\")" match {
case FunctionTemplate2("foo", first, second) =>
println(s"$first,$second")
}
But this is not as cute as it could be. I would like to have something like that:
case FunctionTemplate2("foo")(first, second) =>
println(s"$first,$second")
Like curried extractor. So I tried this:
case class Function2Extractor(fooName: String){
private final val pattern = Pattern.compile("^(.+?)\\((.+?)\\,(.+?)\\)")
println("creating")
def unapply(functionCallExpression: String): Option[(String, String, String)] =
//parse and extract as before
}
But it did not work:
"foo(1, \"str_arg\")" match {
case Function2Extractor("foo")(first, second) =>
println(s"$first,$second")
}
Is there a way to do this in Scala?
You can simply it by using some utilities in Scala toolset
Notice how pattern is used in match case.
Scala REPL
scala> val pattern = "^(.+?)\\((.+?)\\,(.+?)\\)".r
pattern: scala.util.matching.Regex = ^(.+?)\((.+?)\,(.+?)\)
scala> "foo(1, \"str_arg\")" match { case pattern(x, y, z) => println(s"$x $y $z")}
foo 1 "str_arg"
I want to do pattern matching in Scala but it should be case insensitive. Is there a way I can write the code without using separate 'case' clauses for lower and upper cases
//person class with first name and last name
case class Person (var fn: String, val ln: String) {
val name = fn
val lastName = ln
}
//two instances. Same last name but cases are different
val a2 = Person("Andy","Cale")
val a3 = Person("Andy","cale")
def isCale(person:Person) {
person match {
//I want that this case should be case insensitive
case Person(_,"Cale") => println("last-name Cale")
case _ => println("not Cale")
}
}
isCale(a2)
lastname Cale
//I want this to also match
isCale(a3)
not Cale
One alternative is to extract the last name and compare as follows but I am interested in finding if there is a way to do this in case itself.
def isCale(a2:A2) {
val s = a2.ln
s.toLowerCase match {
case "cale" => println("last-name Cale")
case _ => println("not Cale")
}
You can use a guard:
def main(args: Array[String]): Unit = {
case class Person(firstName: String, lastName: String)
val p = Person("Yuval", "Itzchakov")
p match {
case Person(_, lastName) if lastName.equalsIgnoreCase("itzchakov") =>
println(s"Last name is: $lastName")
case _ => println("Not itzchakov")
}
}
Side note - case class parameters will be attached as vals on the declared class, there's no need for the additional assignment and no need for the val/var definition on the constructor.
You can use an extractor:
scala> val r = "(?i:it.*ov)".r
r: scala.util.matching.Regex = (?i:it.*ov)
scala> case class Person(firstName: String, lastName: String)
defined class Person
scala> val ps = Person("Fred", "Itchikov") :: Person("Yuval", "Itzchakov") :: Nil
ps: List[Person] = List(Person(Fred,Itchikov), Person(Yuval,Itzchakov))
scala> ps collect { case Person(_, n # r()) => n }
res0: List[String] = List(Itchikov, Itzchakov)