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.
The match method takes a number of cases as an argument. Each alternative takes a pattern and one or more expressions that will be performed if the pattern matches.
Pattern Matching works by "reading" through text strings to match patterns that are defined using Pattern Matching Expressions, also known as Regular Expressions. Pattern Matching can be used in Identification as well as in Pre-Classification Processing, Page Processing, or Storage Processing.
case _ => does not check for the type, so it would match anything (similar to default in Java). case _ : ByteType matches only an instance of ByteType . It is the same like case x : ByteType , just without binding the casted matched object to a name x .
Explanation: In the above program, if the value of x which is passed in test method call matches any of the cases, the expression within that case is evaluated. Here we are passing 1 so case 1 will be evaluated. case_ => is the default case which will get executed if value of x is not 0 or 1.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With