i am new to scala and got stuck with one pattern matching case.
i am writing a pattern match as below,
case class data(record:String)
def x(input:String)= input match {
case "a" => val a = data("a")
case "b" => val b = data("b")
case anythingElse = val others = ...other object creation
}
i would like to access the variables val a,val b and val others outside of the def x.
is there a way to return val a, val b and val others once the calls to the def x is completed.
Your understanding of pattern matching is not entirely correct, you can assign vals in each case area, but the only purpose of that would be to do something temporary. In scala whatever statement you place last is the return value. This cannot be a val assignment statement, or no value (aka unit) would be returned
def x(input:String)= input match {
case "a" => data("a and something special")
case "b" =>
val temp = "b"+" Hello"
data(temp)
case other => data(other)
}
To my understanding, you are trying to define a new variable in a scope that is outside of each case block:
case class data(record:String)
def x(input:String)= input match {
case "a" => val a = data("a")
case "b" => val b = data("b")
case anythingElse = val others = ...other object creation
}
x("a")
println(a) // should print data("a")
Correct?
That is not going to work! Because Scala "Cannot resolve symbol a". It wasn't defined in the scope where you are using it.
Instead you could propagate it to outer scope using some kind of a data container ( defined in the outer scope ). It could be an array, a variable, or a Map. However this will also make it a mutable container. Not recommended.
A better approach would be to actually return your state from x() method and then use that state. Something like this:
case class Data(record: String)
val template: Map[String, Option[Data]] = List("a", "b", "c").map(_ -> None).toMap[String, Option[Data]]
def x(input: String): Map[String, Option[Data]] = input match {
case "a" => template + ("a" -> Some(Data("a")))
case "b" => template + ("b" -> Some(Data("b")))
case anythingElse => template + ("others" -> Some(Data("others")))
}
val returnMap = x("a")
for (x <- returnMap("a")) {
println(x)
}
OUTPUT:
Data(a)
Values a, b and others are local to each case; consider though
def x(input:String)= input match {
case "a" => (Some(data("a")), None, None)
case "b" => (None, data("b"), None)
case anythingElse = (None, None, `...other object creation`)
}
where the function returns a triplet of Option in which a None represents no matching, and Some(data("...")) a match; hence for instance
val (a,b,others) = x("a")
delivers
a = Some(data("a"))
b = None
others = None
Related
I would like to add an element depending on the result of a different condition.
As it is now, I did it this way :
val add1 = if(condition1) Seq(ENUM_ELEMENT_1) else Seq()
val add2 = if(condition2) Seq(ENUM_ELEMENT_2) else Seq()
return Seq(add1, add2).flatten
If I was in Java I would just create an empty ArrayList() at the beginning and add to this list as the code encounter the ifs.
But in Scala, I would have to use a mutable object of Seq and I don't know if it's appropriate here.
Declare list of tuples with conditions on left and enums on right:
val conditions: Seq[(Boolean, Enum)] = List(
condition1 -> ENUM_ELEMENT1,
condition2 -> ENUM_ELEMENT2
)
Then you can just reduce it with collect:
val result: Seq[String] = conditions.collect{
case (true, v) => v
}
or flatMap:
val result: Seq[Enum] = conditions.flatMap{
case (true, v) => Some(v)
case _ => None
}
There is several ways to do this. Here's what come out of the blue to me:
(if(condition1) Seq(ENUM_ELEMENT_1) else Seq()) ++ (if(condition2) Seq(ENUM_ELEMENT_2) else Seq())
They are way of factorizing both of this procedure by a function or a fold but it may be overthinking at this state.
Without proper context I am unable to provide a more concrete solution, but I think this pseudo-code represents your problem.
If you have any questions, do not doubt to ask for clarification.
object ListFactory {
type Input = ???
type Output = ???
private val allElements: List[Output] =
List(ENUM_ELEMENT_1, ENUM_ELEMENT_2, ???)
private val conditions: List[Input => Boolean] =
List(???)
def apply(input: Input): List[Output] =
(allElements zip conditions).collect {
case (element, condition) if (condition(input)) => element
}
}
ListFactory(???) // res1: List[Output] = ???
I'd like to get the numbers of each of the fruit, based on their type. Obviously I need to accumulate them in some way but what is the best/cleanest/etc?
trait Fruit
case object Apple extends Fruit
case object Pear extends Fruit
case class Orange(variety: String) extends Fruit
val fruits = List(Pear, Apple, Pear, Pear, Apple, Orange("satsuma"), Orange("clementine"))
val numberOfOranges = ???
val numberOfApples = ???
val numberOfPears = ???
Thanks
You can use groupBy with pattern matching:
val counts = fruits.groupBy{
case Apple => "apple"
case Pear => "pear"
case Orange(_) => "orange"
}.map{
case (key, values) => (key, values.size)
}
EDIT If you like reflection, and your subtypes don't have type parameters, you can use fruits.groupBy(_.getClass) (or getClass.getSimpleName, if you want strings).
You can use count with a pattern match to do this:
val numberOfOranges = fruits.count { case Orange(_) => true
case _ => false }
The above will print 2. Just change the pattern match for your other examples.
Here's a solution using reflection, although you need to use a pattern match to bring the types in scope. You can't iterate a list generically and get the types of each individual element.
val orangeType = reflect.runtime.universe.typeOf[Orange]
val appleType = reflect.runtime.universe.typeOf[Apple.type]
val pearType = reflect.runtime.universe.typeOf[Pear.type]
def getType[T: reflect.runtime.universe.TypeTag](obj: T) =
reflect.runtime.universe.typeOf[T]
def typesOf[A](fs: List[A]): List[reflect.runtime.universe.Type] = {
fs.map {
// Use pattern match to reify type
case v # Apple => getType(v)
case v # Pear => getType(v)
case v # Orange(_) => getType(v)
}
}
val fruitCount = typesOf(fruits).groupBy(identity).mapValues(_.size)
val numberOfOranges = fruitCount(orangeType)
val numberOfApples = fruitCount(appleType)
val numberOfPears = fruitCount(pearType)
Honestly, this is just grouping by Scala reflection types rather than something more obvious like strings (or some other primitive type) and quickly becomes overly complicated. Best long-term coding solution is to just come up with your own enumeration and use that, IMO.
You can use getClass in your groupBy:
fruits.groupBy(f => f.getClass.getSimpleName).mapValues(_.size)
// result: Map(Apple$ -> 2, Pear$ -> 3, Orange -> 2)
I want to update a sequence in Scala, I have this code :
def update(userId: Long): Either[String, Int] = {
Logins.findByUserId(userId) map {
logins: Login => update(login.id,
Seq(NamedParameter("random_date", "prefix-" + logins.randomDate)))
} match {
case sequence : Seq(Nil, Int) => sequence.foldLeft(Right(_) + Right(_))
case _ => Left("error.logins.update")
}
}
Where findByUserId returns a Seq[Logins] and update returns Either[String, Int] where Int is the number of updated rows,
and String would be the description of the error.
What I want to achieve is to return an String if while updating the list an error happenes or an Int with the total number of updated rows.
The code is not working, I think I should do something different in the match, I don't know how I can check if every element in the Seq of Eithers is a Right value.
If you are open to using Scalaz or Cats you can use traverse. An example using Scalaz :
import scalaz.std.either._
import scalaz.std.list._
import scalaz.syntax.traverse._
val logins = Seq(1, 2, 3)
val updateRight: Int => Either[String, Int] = Right(_)
val updateLeft: Int => Either[String, Int] = _ => Left("kaboom")
logins.toList.traverseU(updateLeft).map(_.sum) // Left(kaboom)
logins.toList.traverseU(updateRight).map(_.sum) // Right(6)
Traversing over the logins gives us a Either[String, List[Int]], if we get the sum of the List we get the wanted Either[String, Int].
We use toList because there is no Traverse instance for Seq.
traverse is a combination of map and sequence.
We use traverseU instead of traverse because it infers some of the types for us (otherwise we should have introduced a type alias or a type lambda).
Because we imported scalaz.std.either._ we can use map directly without using a right projection (.right.map).
You shouldn't really use a fold if you want to exit early. A better solution would be to recursively iterate over the list, updating and counting successes, then return the error when you encounter one.
Here's a little example function that shows the technique. You would probably want to modify this to do the update on each login instead of just counting.
val noErrors = List[Either[String,Int]](Right(10), Right(12))
val hasError = List[Either[String,Int]](Right(10), Left("oops"), Right(12))
def checkList(l: List[Either[String,Int]], goodCount: Int): Either[String, Int] = {
l match {
case Left(err) :: xs =>
Left(err)
case Right(_) :: xs =>
checkList(xs, (goodCount + 1))
case Nil =>
Right(goodCount)
}
}
val r1 = checkList(noErrors, 0)
val r2 = checkList(hasError, 0)
// r1: Either[String,Int] = Right(2)
// r2: Either[String,Int] = Left(oops)
You want to stop as soon as an update fails, don't you?
That means that you want to be doing your matching inside the map, not outside. Try is actually a more suitable construct for this purpose, than Either. Something like this, perhaps:
def update(userId: Long): Either[String, Int] = Try {
Logins.findByUserId(userId) map { login =>
update(login.id, whatever) match {
case Right(x) => x
case Left(s) => throw new Exception(s)
}
}.sum
}
.map { n => Right(n) }
.recover { case ex => Left(ex.getMessage) }
BTW, a not-too-widely-known fact about scala is that putting a return statement inside a lambda, actually returns from the enclosing method. So, another, somewhat shorter way to write this would be like this:
def update(userId: Long): Either[String, Int] =
Logins.findByUserId(userId).foldLeft(Right(0)) { (sum,login) =>
update(login.id, whatever) match {
case Right(x) => Right(sum.right + x)
case error#Left(s) => return error
}
}
Also, why in the world does findUserById return a sequence???
In the scala console I can do the following without a problem :
scala> val tree = q"def f():MySuperType[(Char,Char)]"
tree: universe.DefDef = def f(): MySuperType[scala.Tuple2[Char, Char]]
scala> val q"def $f():$d" = tree
f: universe.TermName = f
d: universe.Tree = MySuperType[scala.Tuple2[Char, Char]]
scala> val tq"$a[$TheTypeThatIWant]" = d
a: universe.Tree = MySuperType
TheTypeThatIWant: universe.Tree = scala.Tuple2[Char, Char]
And I can get what I want : the content of TheTypeThatIWant
Now If I try to do that inside a quasiquote, I get a match exception and I didn't find a way to get the inner type of an applied type.
My code :
tree match {
case q"{..$body}" =>
body.foreach (_ match {
case q"def $functionName:$type = $osef" =>
val tq"$f[$typ]" = d //I want to get $typ !!
...
}
But all I get is :
exception during macro expansion:
exception during macro expansion:
scala.MatchError: MyMacro.MySuperType[(Char, Char)] (of class scala.reflect.internal.Trees$TypeTree)
at MyMacro$$anonfun$getBasicStructure$1$1.apply(MyMacro.scala:737)
at MyMacro$$anonfun$getBasicStructure$1$1.apply(MyMacro.scala:735)
at scala.collection.immutable.List.foreach(List.scala:383)
at MyMacro$.getBasicStructure$1(MyMacro.scala:735)
at MyMacro$.MyMacro_impl(MyMacro.scala:846)
How can I solve that ?
Thank you
Edit :
The problem is not only with quasiquotes, it bugs even when I work with Trees :
case Block(stats,expr) =>
stats.foreach(_ match {
case DefDef(_,_,_,_,typ,_) =>
typ match {
case AppliedTypeTree(t,args) => //doesnt go there
case TypeApply(t,args) => //doesnt go there
case x:TypeTree => //goes there but can't get any info about the applied type
case _ =>
}
})
Edit2 :
You have to do it that way :
case q"def $name:${d:TypeTree} = $b" =>
d.tpe match {
case TypeRef(x,y,z) => //z is the list of applied types, see scaladoc
case _ =>
}
Well, I guess that's because in the console, by the time you call val tq"$a[$TheTypeThatIWant]" = d , the type of d is actually known, but it's not the case in the macro.
Is there any Scala trick to enable pattern matching against map keys? In other words, I'd like to have an extractor that beside the Map instance accepted also a key value that would mean I want this pattern to match only if the matchable value is an instance of Map and there is an entry with the given key in it and the value for this entry be subject to recursive pattern matching.
Something like this:
myMap match {
case MyMap("a")(a) => // do smth with value a
case MyMap("b")(MyMap("c")(c)) => // do smth with value c
}
Update:
I've found some way to approach closer to the goal, but it's still not perfect because it implies definition of synthetic key-value-holders:
case class MapKey[K](key: K) {
def unapply(o: Any) = o match {
case m: Map[K, _] ⇒ m.get(key)
case _ ⇒ None
}
}
val m1 = Map("a" → "aa", "b" → Map("c" → "cc"))
val m2 = Map("a" → "aa", "d" → "dd")
val b = MapKey("b")
val c = MapKey("c")
val d = MapKey("d")
for (m ← List(m1, m2)) m match {
case b(c(x)) ⇒ println(s"b > c: $x")
case d(x) ⇒ println(s"d: $x")
}
Similar question: Can extractors be customized with parameters in the body of a case statement (or anywhere else that an extractor would be used)?
Feature request: SI-5435
Maybe you are looking for solution that you don't really need? Can't imagine extractors here. You can use PF if you want to match key-value pairs:
val map = Map[String, String]("a" -> "b")
def matchTuple[A,B,C](map: Map[A,B])(pf: PartialFunction[(A,B), C]) =
map.collectFirst(pf)
matchTuple(map) {
case ("a", b) => println("value for a is " + b)
}
Return type is Option[Unit] because we use collectFirst and println