Scala pattern matching not working with Option[Seq[String]] [duplicate] - scala

This question already has answers here:
How do I get around type erasure on Scala? Or, why can't I get the type parameter of my collections?
(11 answers)
Closed 3 months ago.
I am new to Scala(2.13.8) and working on code to use pattern matching to handle a value in different ways, code is very simply like below
def getOption(o: Option[Any]): Unit = {
o match {
case l: Some[List[String]] => handleListData(l)
case _ => handleData(_)
}
}
getOption(Some(3))
getOption(Some(Seq("5555")))
The result is handleListData() been invoked for both input. Can someone help on what's wrong in my code?

As sarveshseri mentioned in the comments, the problem here is caused by type erasure. When you compile this code, scalac issues a warning:
[warn] /Users/tmoore/IdeaProjects/scala-scratch/src/main/scala/PatternMatch.scala:6:15: non-variable type argument List[String] in type pattern Some[List[String]] is unchecked since it is eliminated by erasure
[warn] case l: Some[List[String]] => handleListData(l)
[warn] ^
This is because the values of type parameters are not available at runtime due to erasure, so this case is equivalent to:
case l: Some[_] => handleListData(l.asInstanceOf[Some[List[String]]])
This may fail at runtime due to an automatically-inserted cast in handleListData, depending on how it actually uses its argument.
One thing you can do is take advantage of destructuring in the case pattern in order to do a runtime type check on the content of the Option:
case Some(l: List[_]) => handleListData(l)
This will work with a handleListData with a signature like this:
def handleListData(l: List[_]): Unit
Note that it unwraps the Option, which is most likely more useful than passing it along.
However, it does not check that the List contains strings. To do so would require inspecting each item in the list. The alternative is an unsafe cast, made with the assumption that the list contains strings. This opens up the possibility of runtime exceptions later if the list elements are cast to strings, and are in fact some other type.
This change also reveals a problem with the second case:
case _ => handleData(_)
This does not do what you probably think it does, and issues its own compiler warning:
warn] /Users/tmoore/IdeaProjects/scala-scratch/src/main/scala/PatternMatch.scala:7:28: a pure expression does nothing in statement position
[warn] case _ => handleData(_)
[warn] ^
What does this mean? It's telling us that this operation has no effect. It does not invoke the handleData method with o as you might think. This is because the _ character has special meaning in Scala, and that meaning depends on the context where it's used.
In the pattern match case _, it is a wildcard that means "match anything without binding the match to a variable". In the expression handleData(_) it is essentially shorthand for x => handleData(x). In other words, when this case is reached, it evaluates to a Function value that would invoke handleData when applied, and then discards that value without invoking it. The result is that any value of o that doesn't match the first case will have no effect, and handleData is never called.
This can be solved by using o in the call:
case _ => handleData(o)
or by assigning a name to the match:
case x => handleData(x)
Returning to the original problem: how can you call handleListData only when the argument contains a List[String]? Since the type parameter is erased at runtime, this requires some other kind of runtime type information to differentiate it. A common approach is to define a custom algebraic data type instead of using Option:
object PatternMatch {
sealed trait Data
case class StringListData(l: List[String]) extends Data
case class OtherData(o: Any) extends Data
def handle(o: Data): Unit = {
o match {
case StringListData(l) => handleListData(l)
case x => handleData(x)
}
}
def handleListData(l: List[String]): Unit = println(s"Handling string list data: $l")
def handleData(value: Any): Unit = println(s"Handling data: $value")
def main(args: Array[String]): Unit = {
PatternMatch.handle(OtherData(3))
PatternMatch.handle(StringListData(List("5555", "6666")))
PatternMatch.handle(OtherData(List(7777, 8888)))
PatternMatch.handle(OtherData(List("uh oh!")))
/*
* Output:
* Handling data: OtherData(3)
* Handling string list data: List(5555, 6666)
* Handling data: OtherData(List(7777, 8888))
* Handling data: OtherData(List(uh oh!))
*/
}
}
Note that it's still possible here to create an instance of OtherData that actually contains a List[String], in which case handleData is called instead of handleListData. You would need to be careful not to do this when creating the Data passed to handle. This is the best you can do if you really need to handle Any in the default case. You can also extend this pattern with other special cases by creating new subtypes of Data, including a case object to handle the "empty" case, if needed (similar to None for Option):
case object NoData extends Data
// ...
PatternMatch.handle(NoData) // prints: 'Handling data: NoData'

Related

Scala Multiple Future wrapped in Try

Suppose I have many akka services which all return a case class of type AbcDto wrapped in a Try.
So I call all these services using map and get back a List[Future[Any]].
Now I use Future.sequence to convert this to Future[List[Any]].
How do I unwrap my final list of results? I want to process them only when all of them are a Success and even if one fails I want to throw an error.
I tried mapping Future[List[Any]] as:
val a: List[Future[Any]]
a.map {
case r: List[Success[AbcDto]] => println("hello")
}
But this gives error:
case r: List[Try[AbcDto]]. At this point its giving error: non-variable type argument scala.util.Try[AbcDto] in type pattern List[scala.util.Try[AbcDto]] (the underlying of List[scala.util.Try[AbcDto]])
since all akka services return AbcDtowrapped in a Try the proper type of val a should be List[Future[Try[AbcDto]]]. Now the desired result can be achieved by a combination of Future.sequence and flatMap operation to check for any Failures in the service as shown below.
val a: List[Future[Try[AbcDto]]] = ...
val result: Future[List[AbcDto]] = Future.sequence(a) flatMap {
case r: List[Try[AbcDto]] #unchecked if r.find(!_.isSuccess).isDefined => Future.failed(new RuntimeException("not all results are successful"))
case r => Future.successful(r.collect({ case Success(x) => x}))
}
Future[A] and Try[A] in the actor context are so similar that I see no point to return Try[A] from those actors. You just return A in case of success, which will be Future[A] on asking side, a List of which you can sequence and get Future[List[A]], which, in case of a single failure, will contain the first encountered exception. It seems to be exactly what you're asking for.
To communicate failure from an actor to the asker, you should send akka.actor.Status.Failure with the relevant instance of Throwable.
P.S. regarding comment that using try-catch is non-idiomatic Scala. It actually is. Here's how Try creation is implemented:
object Try {
/** Constructs a `Try` using the by-name parameter. This
* method will ensure any non-fatal exception is caught and a
* `Failure` object is returned.
*/
def apply[T](r: => T): Try[T] =
try Success(r) catch {
case NonFatal(e) => Failure(e)
}
}
As you can see, it uses try-catch inside. If Scala standard library authors are fine with that, so should be you. :)
If I understand you correctly (the types in your question are a bit confusing),
You start with a val responseFutures: List[Future[Any]] and after conversion you have a val responsesFuture: Future[List[Any]]. rogue-ones answer is correct, but it could use some clarification:
Your compiler error is caused by the fact that Success is not a class, but an extractor object with unapply for Try. Therefore you cannot use in in type extraction this way.
So something like case r: List[Try[AbcDto]] if r.forall(_.isSuccess) => println("hello") should compile. However, as AbcDto is erased, you will get a compiler warning about erasure. Thus the #unchecked.
UPDATE
Type erasure means, the compiler cannot check type argument type in pattern matches at compile time. In your case, all the compile knows of your input type is Future[List[Try[Any]]].
So
future.map {
case _: List[Try[AbcDto]] => ???
}
will cause a compiler warning because the compiler only sees.
future.map {
case _: List[Try[_]] => ???
}
The #unchecked annotation just suppresses the corresponding compiler warning.
In the end with the pattern match above you just cast whatever is coming in to Try[AbcDto] without any compile time type safety.

Scala + pattern matching + String autoboxing [duplicate]

This question already has answers here:
How to pattern match on generic type in Scala?
(4 answers)
Closed 6 years ago.
Am reporting a feature that sounds wierd and unable to reason the following behavior with Pattern matching in scala.
def typesPattern(x:Any)= x match{
case s:String⇒ s.length
case n:Map[Int,Int]⇒println("Map[Int,Int]");var a = n.iterator.next();println(a._1);println(a._2);n.size;
case n:Map[a,b]⇒println("Map[a,b]");n.size;
case m:Map[_,_]⇒ m.size
case _ ⇒ -1
}
}
When i invoke above function with following println(typesPattern(Map("a"→10))) i get following error Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at scala.runtime.BoxesRunTime.unboxToInt(BoxesRunTime.java:101)
at scala.Tuple2._1$mcI$sp(Tuple2.scala:20)
First question that I have is "WHY MAP[String->Int] is getting matched with MAP[INT,INT]?", it should have rather matched with MAP[_,_].
Interestingly when I edit the pattern matching code and take out the code that extracts tuple from Map and prints key and value pair
`def typesPattern(x:Any)= x match{
case s:String⇒ s.length
case n:Map[Int,Int]⇒println("Map[Int,Int]");n.size;
case n:Map[a,b]⇒println("Map[a,b]");n.size;
case m:Map[_,_]⇒ m.size
case _ ⇒ -1
}
}`
Now the same invocation like earlier println(typesPattern(Map("a"→10))) does match MAP[INT,INT] without issues and prints the size.
Map[Int,Int]
1
Second question
"WHY THIS TIME SCALA IS ABLE TO MATCH MAP[String->INT] with MAP[INT->INT] (which i still wonder how?) without issues?
You maybe tried to look at warnings compiler gives you?
<console>:12: warning: non-variable type argument Int in type pattern scala.collection.immutable.Map[Int,Int] (the underlying of Map[Int,Int]) is unchecked since it is eliminated by erasure
case n:Map[Int,Int]⇒println("Map[Int,Int]");var a = n.iterator.next();println(a._1);println(a._2);n.size;
^
<console>:13: warning: unreachable code
case n:Map[a,b]⇒println("Map[a,b]");n.size;
Actually, both of these lines:
case n:Map[a,b]⇒println("Map[a,b]");n.size;
case m:Map[_,_]⇒ m.size
are unreachable, because all three lines that match on map are equivalent, at least their pattern will match the same things.
In runtime there is no generic types, they are erased, so Map[A, B] is just a Map. So your only case matching the map is the first one, as they are tested in order
case n:Map[Int,Int]⇒println("Map[Int,Int]");var a = n.iterator.next();println(a._1);println(a._2);n.size;
You get a ClassCastException only when you try to use the values treating them like an Int, because they get cast only if you try to use them. Checking size does not depend on type of its values.
This problem happens because of generics type erasure. In runtime there is no difference between Map of any types. That's why pattern matches at first suitable case.
Simple snippet to check it:
List[String]().isInstanceOf[List[String]] // true
List[String]().isInstanceOf[List[Integer]] // true
It's because of the type erasure. Usage of generic types is of no use in case clauses as it does not retain type information. So MAP[String->Int] is equivalent to Map. That is why MAP[String->Int] match with MAP[Int->Int].
Wouldn't much easier if instead of trying to use pattern matching, you use instead implicits and type classes mechanism?
trait TypePattern[A,B] {
def pattern(a: A):B
}
implicit object stringPattern extends TypePattern[String,Int] {
override def pattern(a: String): Int = a.length
}
implicit object mapIntIntPattern extends TypePattern[Map[Int, Int],Int] {
override def pattern(n: Map[Int, Int]): Int = {
println("Map[Int,Int]")
var a = n.iterator.next()
println(a._1)
println(a._2)
n.size
}
}
implicit object mapAnyPattern extends TypePattern[Map[Any, Any],Int] {
override def pattern(a: Map[Any, Any]): Int = {
println("Map[a,b]")
a.size
}
}
def pattern[A,B](x: A)(implicit typePattern: TypePattern[A,B]): B = {
typePattern.pattern(x)
}

Selector of pattern match being exhaustive

Looking at the Scala doc for sealed classes, it says:
If the selector of a pattern match is an instance of a sealed class, the compilation of pattern matching can emit warnings which diagnose that a given set of patterns is not exhaustive, i.e. that there is a possibility of a MatchError being raised at run-time.
I don't quite understand what they meant in this paragraph. My understanding is that if a switch case, doesn't cover all the possibilities, then we'll get a warning at compile time, saying we might get an error at run time. Is this correct?
I find it strange, because how can we cover ALL the scenarios in a switch case? We would have to match all possible strings, which is just silly, so I take it my understanding is incorrect. Someone care to elucidate, please?
What the paragraph is saying is that in-case you have a fixed hierarchy structure like this:
sealed trait Foo
class Bar extends Foo
class Baz extends Foo
class Zab extends Foo
Then when you pattern match on it, the compiler can infer if you've attempted to match on all possible types extending the sealed trait, in this example:
def f(foo: Foo) = foo match {
| case _: Bar => println("bar")
| case _: Baz => println("baz")
| }
<console>:13: warning: match may not be exhaustive.
It would fail on the following input: Zab()
def f(foo: Foo) = foo match {
^
f: (foo: Foo)Unit
Note the beginning of the document says:
A sealed class may not be directly inherited, except if the inheriting
template is defined in the same source file as the inherited class.
This is unique to Scala, and can't be done in Java. A final class in Java cannot be inherited even if declared inside the same file. This is why this logic won't work for String in Scala, which is an alias for java.lang.String. The compiler warning may only be emitted for Scala types that match the above criteria.
I find it strange, because how can we cover ALL the scenarios in a switch case? We would have to match all possible strings
Yes, if the selector has type String (except it isn't a sealed class, because that's a Scala concept and String is a Java class).
which is just silly
No. For strings, you just need a catch-all case, e.g.
val x: String = ...
x match {
case "a" => ...
case "b" => ...
case _ => ...
}
is an exhaustive match: whatever x is, it matches one of the cases. More usefully, you can have:
val x: Option[A] = ...
x match {
case Some(y) => ...
case None => ...
}
and the compiler will be aware the match is exhaustive even without a catch-all case.
The wildcard character allows us to cover all the scenarios.
something match {
case one => ...
case two => ...
case _ => ...
}
It is selected whenever all other cases don't match; that is, it is the default case. Further information here.

Reify a ValDef from compile to runtime

I want to reify a ValDef into runtime, but i does not work directly. If i encapsulate the ValDef into a Block, everything works perfectly, like in the following example:
case class Container(expr: Expr[Any])
def lift(expr: Any): Container = macro reifyValDef
def reifyValDef(c: Context)(expr: c.Expr[Any]): c.Expr[Container] = {
import c.universe._
expr.tree match {
case Block(List(v: ValDef), _) =>
val asBlock = q"{$v}"
val toRuntime = q"scala.reflect.runtime.universe.reify($asBlock)"
c.Expr[Container](q"Container($toRuntime)")
}
}
lift {
val x: Int = 10
}
If i would use v directly, instead of wrapping it into a block, I get the error:
Error:(10, 11) type mismatch;
found :
required: Any
Note that extends Any, not AnyRef.
Such types can participate in value classes, but instances
cannot appear in singleton types or in reference comparisons.
val x: Int = 10
^
Is it just not working directly with ValDefs or is something wrong with my code?
That's one of the known issues in the reflection API. Definitions are technically not expressions, so you can't e.g. pass them directly as arguments to functions. Wrapping the definition in a block is a correct way of addressing the block.
The error message is of course confusing, but it does make some twisted sense. To signify the fact that a definition by itself doesn't have a type, the tpe field of the corresponding Tree is set to NoType. Then the type of the argument of a macro is checked against Any and the check fails (because NoType is a special type, which isn't compatible with anything), so a standard error message is printed. The awkward printout is an artifact of how the prettyprinter behaves in this weird situation.

Zero-arg pattern matches when one arg expected

Given this definition in Scala:
class Foo(val n: Int)
object Foo {
def unapply(foo: Foo): Option[Int] = Some(foo.n)
}
This expression compiles and returns ok:
new Foo(1) match {
case Foo() => "ok"
}
Why does this even compile? I would expect that an extractor with Option[T] implies matching patterns with exactly one argument only.
What does the pattern Foo() mean here? Is it equivalent to Foo(_)?
In other words, what is the language rule that enables the experienced behavior.
Today (in 2.11 milestone) you get the error:
<console>:15: error: wrong number of patterns for object Foo offering Int: expected 1, found 0
case Foo() => "ok"
^
I encountered this when adding Regex.unapply(c: Char). At some point, the case you point out was accepted, then later rejected. I remember I liked the idea that if my extractor returns Some(thing), then the Boolean match case r() would work the same as case r(_).
What works is in the scaladoc of unapply(Char) :
http://www.scala-lang.org/files/archive/nightly/docs-master/library/#scala.util.matching.Regex
Section 8.18 of the Scala Language Reference discusses this type of pattern matching. According to the reference, for a pattern like Foo(), it should only match if unapply returns a boolean. If unapply returns Option[T] for some T that isn't a tuple, then the pattern must include exactly one parameter, e.g. Foo(_). Unless I'm really misunderstanding what is happening here, it looks like this is an edge case where the compiler violates the spec.