How does one combine (in a nice way) two Scala match'es?
First I have to test if an Option is a valid value:
myOption match {
case Some(op) =>
doSomethingWith(op)
case None =>
handleTheError()
Then if op was valid, I want to test for another pattern:
Path(request.path) match {
case "work" => {
println("--Let's work--")
}
case "holiday" => {
println("--Let's relax--")
}
case _ => {
println("--Let's drink--")
}
}
I could combine them like this:
myOption match {
case Some(op) =>
doSomethingWith(op)
Path(request.path) match {
case "work" => {
println("--Let's work--")
}
case "holiday" => {
println("--Let's relax--")
}
case _ => {
println("--Let's drink--")
}
}
case None =>
handleTheError()
But, it feels sloppy. Is there a better way to combine them in some way or another.
Update
Apologies, I should have explained better. I'm actually trying to find out if there is a known pattern for simplifying (or factoring out) these control structures. For example (imagine this was true):
x match {
case a => {
y match {
case c => {}
case d => {}
}
}
case b => {}
}
equals
x -> y match {
case a -> c => {}
case a -> d => {}
case b => {}
}
I was just wandering if someone has already identified some refactoring patterns for control flow, much like algebra where 2(x + y) = 2x + 2y
You can do
myOption map { success } getOrElse handleTheError
or with scalaz,
myOption.cata(success, handleTheError)
where success is something like
def success(op: Whatever) = {
doSomethingWith(op)
Path(request.path) match {
case "work" => println("--Let's work--")
case "holiday" => println("--Let's relax--")
case _ => println("--Let's drink--")
}
}
Update
Your pseudocode
x -> y match {
case a -> c => {}
case a -> d => {}
case b => {}
}
can be literally translated to scala as
(x, y) match {
case (a, c) => {}
case (a, d) => {}
case (b, _) => {}
}
It looks nice (and that's probably what you wanted) if inner matcher have only few options (c and d in this case), but it leads to code duplication (repeating of pattern a). So, in general I'd prefer map {} getOrElse {}, or separation of pattern-matchers on smaller functions. But I repeat, in your case it looks reasonable.
If the scrutinees (the expressions on which you match) do not depend on each other, i.e. you can compute them independently, you could perform both matches at the same time by wrapping the scrutinees in a tuple.
So your example:
x match {
case a => {
y match {
case c => fooC(...)
case d => fooD(...)
}
}
case b => fooB(...)
}
could be rewritten as:
(x,y) match {
case (a, c) => fooC(...)
case (a, d) => fooD(...)
case (b, _) => fooB(...)
}
...as long as y doesn't depend on x.
Related
I have an Option of String which may be empty too. I have written following code to handle different branches of flow:
input match {
case Some(val) => {
val match {
case "sayHi" => "Hi"
case "sayHello" => "Hello"
case _ => extractFromAnotherInput
}
}
None => extractFromAnotherInput
}
private def extractFromAnotherInput = {
anotherInput match {
case a => ....
case b => ....
}
}
Is this a good way of handling code branches in a functional language or it can be done in a better way?
You don't have to nest the matches:
input match {
case Some("sayHi") => "Hi"
case Some("sayHello") => "Hello"
case _ => extractFromAnotherInput
}
You can also combine this input with "another", and "strip" he option before doing the match:
input.getOrElse(anotherInput) match {
case "sayHi" => "Hi"
case "sayHello" => "Hello"
case a => ...
case b => ...
}
I would go with Dima's solution. Or if you want to use some method on Option:
input.collect{
case "sayHi" => "Hi"
case "sayHello" => "Hello"
}
.getOrElse(extractAnotherInput)
Just because, I would like to propose an alternative to Levi's answer; just to remark this is more of a matter of opinion.
input.filter(_.nonEmpty).fold(ifEmpty = extractAnotherInput) {
case "sayHi" => "Hi"
case "sayHello" => "Hello"
}
This is a matter of style: I personally dislike pattern-matching on Option, but that's idiosyncratic (I also may be the only Scala programmer who generally dislikes the for notation, but that's neither here nor there).
I'd express that as
input.flatMap { v =>
v match {
case "sayHi" => Some("Hi")
case "sayHello" => Some("Hello")
case _ => None
}
}.getOrElse(extractAnotherInput)
If overfitting to this example (exploiting the fact that I can uniformly transform sayHi and sayHello)
input.filter(v => v == "sayHi" || v == "sayHello")
.map(_.drop(3))
.getOrElse(extractAnotherInput)
I want to pattern match using the same "case" to 2 different case classes but I am getting an error that the binded variable name is already in use:
for instance this:
statement match {
case FirstCase(a,b) => {lots of logic using "a" and "b"}
case SecondCase( obj : FirstCase, somethingElse) => {same logic as above}
...
...
}
I'd love to re-use the same logic :
statement match {
case obj: FirstCase | SecondCase( obj : FirstCase, somethingElse) =>
{lots of logic using "obj.a" and "obj.b"}
...
...
}
But I am getting a compile error "obj is already defined in scope"
Is it possible to "re-use" the name of the binded object?
You can use helper method:
def hlp(a: TypeOfA, b: TypeOfB) = ???
statement match {
case FirstCase(a, b) => hlp(a, b)
case SecondCase(FirstCase(a, b), somethingElse) => hlp(a, b)
}
A couple of alternatives to the other answer:
You can use intermediate variables:
val (a,b) = statement match {
case FirstCase(a,b) => a -> b
case SecondCase(FirstCase(a,b), somethingElse) => a -> b
}
// Use a and b
You can create an extractor object
object AB {
def unapply(x: Any) = x match {
case FirstCase(a,b) => Some(a -> b)
case SecondCase(FirstCase(a,b), somethingElse) => Some(a -> b)
}
}
statement match {
case AB(a,b) => // use a and b here (you don't need the braces btw
}
Assume the following data-structure.
sealed abstract class Formula {...}
//... some other case classes
sealed abstract class BinaryConnective(f0: Formula, f1: Formula) extends Formula {
def getf0 = f0
def getf1 = f1
}
object BinaryConnective {
def unapply(bc : BinaryConnective) = Some((bc.getf0, bc.getf1))
}
final case class Conjunction(f0: Formula, f1: Formula) extends BinaryConnective(f0,f1)
final case class Disjunction(f0: Formula, f1: Formula) extends BinaryConnective(f0,f1)
final case class Implication(f0: Formula, f1: Formula) extends BinaryConnective(f0,f1)
final case class Equivalence(f0: Formula, f1: Formula) extends BinaryConnective(f0,f1)
I now wrote a function that has a lot of pattern-matching:
The return-type of getCondition is Formula => Option[HashMap[Variable, Formula]]
formula match {
//.. irrelevant cases not shown
case Conjunction(f0, f1) => (g : Formula) => {
g match {
case conj # Conjunction(g0, g1) => {
getCondition(f0)(conj.f0) match {
case Some(map0) => {
getCondition(f1)(conj.f1) match {
case Some(map1) if map0.forall{case (key, value) => map1.get(key).map(_ == value).getOrElse(true)} => {
Some(map0 ++ map1)
}
case _ => None
}
}
case None => None
}
}
case _ => None
}
}
}
Now to my question.
1) Is there a nicer way to express this code? A lot of matches going on.
Edit 1: I could not think of a nice-looking way to use things like map, filter etc.., but it seems very compact with for-comprehensions. I've also noticed that conj # was not necessary at all, which also made it a little simpler.
case Conjunction(f0, f1) => (g: Formula) => g match {
case Conjunction(g0, g1) => for {
map0 <- getCondition(f0)(g0)
map1 <- getCondition(f1)(g1)
if map0.forall {case (key, value) => map1.get(key).map(_ == value).getOrElse(true)}
} yield map0 ++ map1
case _ => None
}
2) This is the match for Conjunction. I would have to repeat it for Disjunction, Implication and Equivalence. g has to be of the same class as formula. The only thing that would change is case conj # Conjunction(g0, g1). I would have to adjust it to case disj # Disjunction(g0, g1) if formula is a Disjunction etc...
Is there a way to do it combined for all cases?
Option should provide a lot of useful functions to simplify your code.
For example, when you write something like:
o match {
case Some(e) => Some(transform(e))
case _ => None
}
You could just call map: o.map(transform)
I also invite you to look at the filter function for the cases including a condition.
EDIT: great suggestion by #om-nom-nom: For comprehensions can also be used (they actually are sugar relying on map, flatMap, filter, etc):
for{
e <- o
} yield transform(e)
I have an automatically generated client for an web service. I has many complicated classes and I have to do a pattern matching against it. For now I have a structure looking like this:
val response = client.getResponse
response match {
case Left(_) => None
case Right(a: SomeClass) => a match {
case SomeClass2(b: Option[SomeClass3]) => b match {
case None => None
case Some(c: SomeClass3) => c match {
case SomeClass4(_, _, _, _, d: Seq[SomeClass4]) => d match {
case Nil => None
case seq: Seq[SomeClass5] => seq match {
case Nil => None
case Seq(xs#_*) => xs map { x =>
x match {
case Nil => None
case SomeClass6(e: SomeClass7) => e match {
case Nil => None
case SomeClass8(f, _, _, _, _) => f match {
case Nil => None
case Seq(xs#_*) => xs map { x =>
x match {
case Nil => None
case SomeClass9(g: Seq[SomeClass9], _, _, _, _, _, _, _, _, _, _) => /* + some nested levels more*/
}
}
}
}
}
}
}
}
}
}
}
where SomeClass1 - SomeClass9 are case classes.
As you can see, it seems frightening. What do I do about it? What's the standard way to make it look nicer?
I guess there should be not only refactoring but rather another approach.
Assuming that a should be SomeClass2, but not SomeClass (same with b, c, d).
You could use alternative patterns like case A | B => ... and structural patterns like Some(MyClass(f)).
Also you could use partial function in map like map { case ... } instead of map { x => x match {...} }.
And I guess there is a error in your code: there is check for case Nil => ...; case SomeClass8(...) => ....
You could replace Seq(xs #_*) with xs. If you need entire collection you don't need to extract elements.
Your code:
response match {
case Left(_) | Right(SomeClass2(None)) | Right(SomeClass2(Some(SomeClass3(_, _, _, _, Nil))) => None
case Right(SomeClass2(Some(SomeClass3(_, _, _, _, xs))) =>
xs map {
case SomeClass6(None) | SomeClass6(Some(SomeClass8(Nil, _, _, _, _))) => None
case SomeClass6(Some(SomeClass8(xs, _, _, _, _))) =>
xs map {
case Nil => None
case SomeClass9(g, _, _, _, _, _, _, _, _, _, _) => /* + some nested levels more*/
}
}
}
You should also extract nested matches to separate methods.
Pattern matching is not the only solution. You could use methods of Either and Option:
response.right.toOption.collect {
// No need for the first part.
case SomeClass2(Some(SomeClass3(_, _, _, _, xs)) if xs.nonEmpty => ...
}
You might find extractors useful. It might also be worth flattening some of the cases so you have
case Right(SomeClass(SomeClass2(Some(SomeClass3(value))))) => value ...
case _ => None
rather than having a None case explicitly defined at each level.
You can probably reduce a lot of this complexity by using for comprehensions rather than pattern matching.
One simple opportunity is where you are mapping a sequence to yet another pattern match:
case seq: Seq[SomeClass5] => seq match {
case Nil => None
case Seq(xs#_*) => xs map { x =>
x match {
...
}
}
}
This is very ugly because you have used match to eliminate the Nil case and then matched seq again. Two levels of match to deal with one object. This could become
case seq: Seq[SomeClass5] => for (x <- seq) yield {
x match {
...
}
}
This eliminates the Nil case check and removes a couple of layers of nesting, which is a big win. And you do this in at least two layers, so that's an even bigger win. Of course, this returns a (possibly Nil) sequence rather than f(x) or None, but you can easily convert that. One way to do this, without adding another of nesting, would be this:
case seq: Seq[SomeClass5] => (for (x <- seq) yield {
x match {
...
}
}) match {
case Nil => None
case Seq(i) => Some(i)
case ...
}
Or, if (as I suspect) you expect these sequences only to have one element in them...
case seq: Seq[SomeClass5] => (for (x <- seq) yield {
x match {
...
}
}) match {
case Seq(i) => Some(i)
case _ => None
}
Say I have the following
case class IntWrap(value:Int)
I would like to extract the same variable from two cases as follows:
x match {
case value:Int | IntWrap(value) => dosomethingwith(x)
case _ => ???
}
but the only way I have been able to do this is as:
x match {
case value:Int => dosomethingwith(x)
case IntWrap(value) => dosomethingwith(x)
case _ => ???
}
Is there a better way, as in my real life case dosomething is actually a large block of code which is not so easy to encapsulate.
If it is really the case that you want to do something with x, not with the extracted value, then the following would work:
case class IntWrap(value:Int) // extends T
def dosomethingwith(x: Any) = x
val x: Any = IntWrap(101)
x match {
case _: Int | _: IntWrap => dosomethingwith(x)
case _ => ???
}
If you actually want to work with the extracted value, you could factor out the corresponding match block into its own extractor and reuse that wherever necessary:
x match {
case Unwrap(value) => dosomethingwith(value)
case _ => ???
}
object Unwrap {
def unapply(x: Any) = x match {
case x: Int => Some((x))
case IntWrap(value) => Some((value))
case _ => None
}
}
I honestly don't see an issue with the way you are doing things. As long as dosomethingwith is a separate function then I don't see any issues with duplicate code. If your code looked like this then I don't see any need to come up with other solutions:
def foo(x:Any){
x match {
case value:Int => dosomethingwith(value)
case IntWrap(value) => dosomethingwith(value)
case _ => ???
}
}
def dosomethingwith(x:Int){
//do something complicated here...
}
I came up with sth a little bit different, but it may help you avoid duplicates:
case class IntWrap(value: Int)
implicit def intWrapToInt(intWrap: IntWrap) = intWrap.value
def matchInt(x: AnyVal) = x match {
case i: Int => println("int or intWrap")
case _ => println("other")
}
//test
matchInt(IntWrap(12)) //prints int or intWrap
matchInt(12) //prints int or intWrap
matchInt("abc") //prints other
It won't work for every reference, though. So, be careful.