Scala multi-level pattern matching - scala

I'm stuck with multi-level pattern matching, in the code below I want to match one particular case which is checked at several levels "cfe is Assignment, assignmentCfe.getRight is BinaryExpression, and so on", the solution looks ugly and I hope there is something better Scala can offer me. :)
def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
val fa = new FlowAnalyzer
val states = fa.analyze(cfg)
states.foreach { case (cfe, state) => cfe match {
case assign: Assignment => assign.getRight match {
case expression: BinaryExpression => expression.getOperator match {
case Operator.Div | Operator.Rem => processDivisions()
case _ =>
}
case _ =>
}
case _ =>
}
case _ =>
}
}
How to get rid of these empty default cases in the end?
Another approach would be using nested conditions, but IntelliJ IDEA offers me to replace these conditions back to pattern matching
states.foreach { case (cfe, state) => if (cfe.isInstanceOf[Assignment]) {
val assignment = cfe.asInstanceOf[Assignment]
if (assignment.getRight.isInstanceOf[BinaryExpression]) {
val expression = assignment.getRight.asInstanceOf[BinaryExpression]
if (expression.getOperator == Operator.Div || expression.getOperator == Operator.Rem) processDivisions()
}
}}

Are Assignment and BinaryExpression themselves case classes? Or do they have corresponding unapply methods? If so, then you can nest pattern matches and ignore fields you don't care about. For example, something like:
def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
val fa = new FlowAnalyzer
val states = fa.analyze(cfg)
states.foreach {
case (Assignment(_, BinaryExpression(_, _, Operator.Div | Operator.Rem)), _) => processDivisions()
case _ =>
}
}
This will at least cut the number of default matches down to 1.
If these are not case classes or don't have extractors, then you could consider writing your own if this is a common enough (anti)pattern in your code: http://docs.scala-lang.org/tutorials/tour/extractor-objects.html

One other idea is you could use the "pimp my library" pattern to define an implicit conversion from any object into a class that can do a kind of partial matching:
class PartialMatcher[A](a: A) {
def partialMatch(f: PartialFunction[A, Unit]): Unit = if (f.isDefinedAt(a)) f(a)
}
implicit def makePartialMatcher[A](a: A) = new PartialMatcher(a)
Then just replace all of those matches with partialMatch:
def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
val fa = new FlowAnalyzer
val states = fa.analyze(cfg)
states.foreach { case (cfe, state) => cfe partialMatch {
case assign: Assignment => assign.getRight partialMatch {
case expression: BinaryExpression => expression.getOperator partialMatch {
case Operator.Div | Operator.Rem => processDivisions()
}
}
}}
}
Note that there are other reasons why you might avoid this kind of thing... overusing implicit conversions can make understanding code a lot more difficult. It's a stylistic choice.

Use .collect:
def findAll(cfg: Cfg, consumer: Consumer[Violation]): Unit = {
val fa = new FlowAnalyzer
val states = fa.analyze(cfg)
states.collect { case (assign: Assignment, _) =>
assign.getRight
}.collect { case expression: BinaryExpression =>
expression.getOperator
}.collect { case Operator.Div | Operator.Rem =>
processDivisions
}

Related

How to negate types in pattern match in Scala?

With this code println will be executed only for specified exception. I'm wondering if it's possible to negate that line to make it executed for all other exceptions that are not specified. I know it's possible using 2 cases, but I want to know if it can be done with one case.
val myHandler: PartialFunction[Throwable, Unit] = {
case e # (_: MappingException | _: ParseException | _: SomeOtherException) =>
println("Got it")
}
AFAIk you can not do this with a single match, but you can create your own custom Extractor in case you need to replicate this behaviour in multiple places.
import scala.reflect.ClassTag
final class Not[A : ClassTag] {
def unapply(any: Any): Boolean = any match {
case _: A => false
case _ => true
}
}
object Not {
def apply[A : ClassTag]: Not[A] = new Not
}
which you can use like this:
final val NotAnInt = Not[Int]
10 match {
case NotAnInt() => false
case _ => true
}
// res: Boolean = true
"10" match {
case NotAnInt() => true
case _ => false
}
// res: Boolean = true
However, keep in mind this will have all the limitation of any type check, like not being able to differentiate between a List[Int] from a List[String] due erasure; and being considered a bad practice.
I would suggest looking into a typeclass approach, for example, I believe Shapeless provides a negation one.
You can see the code running here.
Well you've already identified what is probably the more readable way to do it.
val myHandler: PartialFunction[Throwable, Unit] = {
case e # (_: MappingException | _: ParseException | _: SomeOtherException) =>
throw e
case _ =>
println("Got it")
}
This is probably how I'd write this in actual production code. It's sensible and it's clear at a glance.
But you asked for one case, so let's give that a go. Since we want to check against several types, we'll need to be able to represent them as a list. There are countless Scala libraries that make this prettier, but for our purposes we'll just roll our own.
trait TList {
def isMember(x: Any): Boolean
}
object Nil extends TList {
def isMember(x: Any) = false
}
case class Cons[H : ClassTag](val tail: TList) extends TList {
def isMember(x: Any) = {
x match {
case _: H => true
case _ => tail.isMember(x)
}
}
}
So we can represent classical Lisp-style singly-linked lists and check whether an arbitrary Any value has a type anywhere in the list. Now let's negate it and write an unapply method.
case class NotMember(val types: TList) {
def unapply(elem: Any): Boolean =
!types.isMember(elem)
}
Then our handler looks like
val test = NotMember(
Cons[MappingException](Cons[ParseException](Cons[SomeOtherException](Nil)))
)
val myHandler: PartialFunction[Throwable, Unit] = {
case test() =>
println("Got it")
}
Again, if you really want to go down this road, you'll want to grab a library to make the type-level stuff manageable. But it's definitely possible. The only question is whether it's worth it for your use case.

More efficient Solution with tailrecursion?

I have the following ADT for Formulas. (shortened to the important ones)
sealed trait Formula
case class Variable(id: String) extends Formula
case class Negation(f: Formula) extends Formula
abstract class BinaryConnective(val f0: Formula, val f1: Formula) extends Formula
Note that the following methods are defined in an implicit class for formulas.
Let's say i want to get all variables from a formula.
My first approach was:
Solution 1
def variables: Set[Variable] = formula match {
case v: Variable => HashSet(v)
case Negation(f) => f.variables
case BinaryConnective(f0, f1) => f0.variables ++ f1.variables
case _ => HashSet.empty
}
This approach is very simple to understand, but not tailrecursive. So I wanted to try something different. I implemented a foreach on my tree-like formulas.
Solution 2
def foreach(func: Formula => Unit) = {
#tailrec
def foreach(list: List[Formula]): Unit = list match {
case Nil =>
case _ => foreach(list.foldLeft(List.empty[Formula])((next, formula) => {
func(formula)
formula match {
case Negation(f) => f :: next
case BinaryConnective(f0, f1) => f0 :: f1 :: next
case _ => next
}
}))
}
foreach(List(formula))
}
Now I can implement many methods with the help of the foreach.
def variables2 = {
val builder = Set.newBuilder[Variable]
formula.foreach {
case v: Variable => builder += v
case _ =>
}
builder.result
}
Now finally to the question. Which solution is preferable in terms of efficieny? At least I find my simple first solution more aesthetic.
I would expect Solution 2 to be more efficient, because you aren't create many different HashSet instances and combining them together. It is also more general.
You can simplify your Solution 2, removing the foldLeft:
def foreach(func: Formula => Unit) = {
#tailrec
def foreach(list: List[Formula]): Unit = list match {
case Nil =>
case formula :: next => {
func(formula)
foreach {
formula match {
case Negation(f) => f :: next
case BinaryConnective(f0, f1) => f0 :: f1 :: next
case _ => next
}
}
}
}
foreach(List(formula))
}

Applying or operation to Option results

I have the following code:
class CSplit(var s1: CanvNode, var s2: CanvNode) extends SplitPane
{
topComponent = s1.merge
bottomComponent = s2.merge
def containsV(orig: MapCanvT): Option[MapCanvT] =
{
def containsIn(cn: CanvNode): Option[MapCanvT] = cn match
{
case Left => None
case Right(mc) => if (mc == orig) Some(mc) else None
}
containsIn(s1) match
{
case Some(mc) => Some(mc)
case None => containsIn(s2)
}
}
}
I want to reduce the code of the containsV method. My first thought was to use a fold method to shorten the containsIn method. But Option doesn't have one, nor does it extend Class Either. Shouldn't Option[T] extend Either[T, None] ? Then at least one could use Either's fold method.
My final thought was to treat s1 and s2 as a List and do find over it but I can't get this to compile:
def containsV(orig: MapCanvT):
Option[MapCanvT] = ::[CanvNode](s1, s2).find(_ == Right(orig))
Scala 2.10 adds fold to Option. In the meantime you can use map(f).getOrElse(g) instead:
// These produce identical results
o.fold(g)(x => f(x))
o.map(x => f(x)).getOrElse(g)
Edit: so, for example, the following three do the same thing:
val os: List[Option[Int]] = List(Some(5),None)
// Explicit match
os.map{ _ match {
case Some(x) => x+3
case None => 0
}}
// map+getOrElse
os.map{ _.map(_+3).getOrElse(0) }
// fold
os.map{ _.fold(0)(_+3) }
In the fold case, you give the default value for the None case first, and then the function that handles the case where there is a value. In each case you should get List(8,0).
It can be implemented with a list by using the collectFirst method
def containsV(orig: MapCanvT): Option[MapCanvT]
= List(s1, s2).collectFirst {case i: MapCanvT if (i == (orig) => i}
Let's start with the easy part:
containsIn(s1) match
{
case Some(mc) => Some(mc)
case None => containsIn(s2)
}
is the same as
containsIn(s1) orElse containsIn(s2)
Now we only have to deal with containsIn:
def containsIn(cn: CanvNode): Option[MapCanvT] = cn match
{
case Left => None
case Right(mc) => if (mc == orig) Some(mc) else None
}
We can use fold on Either, which gets rid of most of the pattern matching:
cn.fold(_ => None, Some(_))
But there's the orig thingy. We can handle it with a filter, though:
cn.fold(_ => None, Some(_)) filter (orig.==)
Thus:
def containsV(orig: MapCanvT): Option[MapCanvT] = {
def containsIn(cn: CanvNode): Option[MapCanvT] =
cn.fold(_ => None, Some(_)) filter (orig.==)
containsIn(s1) orElse containsIn(s2)
}
I think orElse is much overlooked.

How to pattern match on Scala's parser combinator result

We have a multithreaded RPC server that parses input strings. We've run into an issue where Scala's parser combinator library is not multithreaded safe: the var lastNoSuccess in Parsers.scala is used by any parsing. We get a NullPointerException in this line
if (!(lastNoSuccess != null && next.pos < lastNoSuccess.next.pos))
The default way to implement the parser by making an object that extends one of the Parsers, but I want to construct a parser on demand so each has its own internal state, so I'm using a class instead of an object. However, I can't get it to compile since I need to pattern match on the result:
import scala.util.parsing.combinator.RegexParsers
class SqlParserImpl
extends RegexParsers
{
val term: Parser[String] = """(?i)term\b""".r
}
object Test
{
def main(args: Array[String]): Unit =
{
val parser = new SqlParserImpl
parser.parseAll(parser.term, "term") match {
// How do I match?
case SqlParserImpl#Success(result, _) => true
case SqlParserImpl#NoSuccess => false
}
}
}
Fails with
t.scala:16: error: '=>' expected but '#' found.
case SqlParserImpl#Success(result, _) => true
^
t.scala:17: error: '=>' expected but '#' found.
case SqlParserImpl#NoSuccess => false
^
two errors found
Use this:
val parser = new SqlParserImpl
parser.parseAll(parser.term, "term") match {
case parser.Success(result, _) => true
case parser.NoSuccess(_, _) => false
}
The # sign is used to designate a type member. In your case it's using a constructor or an extractor pattern which needs to reference to an object or something that looks like a constructor.
Hmm. I don't have a 2.7 handy. Try this:
parser.parseAll(parser.term, "term") match {
case parser.Success(result, _) => true
case parser.Failure(_, _) => false
case parser.Error(_, _) => false
}
I was able to compile the following:
object Test {
def main(args: Array[String]): Unit = {
val parser = new SqlParserImpl
println(parser.parseAll(parser.term, "term") match {
case x: parser.Success[_] => true
case x: parser.NoSuccess => false
})
}
}
The NoSuccess object (with extractor) was added back in 2009, at a time when no code was being backported to 2.7 anymore It's implementation, however, is pretty simple:
object NoSuccess {
def unapply[T](x: ParseResult[T]) = x match {
case Failure(msg, next) => Some(msg, next)
case Error(msg, next) => Some(msg, next)
case _ => None
}
}
So you can replace the parser.NoSuccess(_, _) match with one parser.Failure(_, _) and one parser.Error(_, _) match. But if you are not interested in what is being returned, then it's simpler to match against the type:
case _: parser.Success[_] => true
case _: parser.NoSuccess => false
Like suggested by Eugene.

Is it a rule that unapply will always return an Option?

I tried to create an unapply method to use in pattern matching, and I tried to make it return something different than Option, however, Eclipse shows that as an error. Is it a rule that unapply must return an Option[T] ?
EDIT: here's the code I'm trying to use. I switched the code from the previous section so that unapply returns a Boolean
import java.util.regex._
object NumberMatcher {
def apply(x:String):Boolean = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return matcher.find
}
def unapply(x:String):Boolean = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return matcher.find
}
}
object x {
def main(args : Array[String]) : Unit = {
val strings = List("geo12","neo493","leo")
for(val str <- strings) {
str match {
case NumberMatcher(group) => println(group)
case _ => println ("no")
}
}
}
}
Eclipse says wrong number of arguments for object NumberMatcher. Why is that?
If you want to return something with unapply, return it inside Some. Returning Boolean just tests if the match can be made or not.
Here is how a pattern matching is translated:
str match {
case NumberMatcher(group) => println(group)
case _ => println("no")
}
Assuming NumberMatcher returns Option[...], it will do:
val r1 = NumberMatcher.unapply(str)
if (r1 != None) {
val group = r1.get
println(group)
} else {
println("no")
}
If NumberMatcher returns Boolean, then you can have it receive something. In that case, this is what happens:
str match {
case NumberMatcher() => println("yes")
case _ => println("no")
}
becomes
val r1 = NumberMatcher.unapply(str)
if (r1) {
println("yes")
} else {
println("no")
}
Note that this is a very superficial explanation. Case matches can test for constants, have additional guard conditions, alternatives, use unapply recursively, use unapplySeq, etc. Here I'm only showing very basic usage to address a specific question. I strongly advise searching for a fuller explanation of pattern matching.
Take a look at this example again.
I quote
The return type of an unapply should be chosen as follows:
* If it is just a test, return a Boolean. For instance case even()
* If it returns a single sub-value of type T, return a Option[T]
* If you want to return several sub-values T1,...,Tn, group them in an optional tuple Option[(T1,...,Tn)].
When you defined unapply to return a Boolean, you were indicating that the pattern doesn't have any wildcards to match (or bind). So the case statement for this unextractor should be case NumberMatcher => println(str), and giving it a variable to fill is wrong.
Alternatively, to make case NumberMatcher(group) => println(group) you need to define unapply() to return Option[String]
package com.tutorial.extracters
object ExtracterwithBooleanReturnType extends App {
import java.util.regex._
object NumberMatcher {
def apply(x: String*) = {
x
}
def unapply(x: String): Option[Boolean] = {
val pat = Pattern.compile("\\d+")
val matcher = pat.matcher(x)
return Some(matcher.find)
}
}
val strings = NumberMatcher("geo12", "neo493", "leo")
for (str <- strings) {
str match {
case NumberMatcher(group) => println(group)
case _ => println("no")
}
}
}
we can achieve this with above code as well