How to pattern match large Scala case classes? - scala
Consider the following Scala case class:
case class WideLoad(a: String, b: Int, c: Float, d: ActorRef, e: Date)
Pattern matching allows me to extract one field and discard others, like so:
someVal match {
case WideLoad(_, _, _, d, _) => d ! SomeMessage(...)
}
What I would like to do, and what's more relevant when a case class has ~20 odd fields, is to extract only a few values in a way that does not involve typing out WideLoad(_, _, _, _, _, some, _, _, _, thing, _, _, interesting).
I was hoping that named args could help here, although the following syntax doesn't work:
someVal match {
case WideLoad(d = dActor) => dActor ! SomeMessage(...)
// ^---------- does not compile
}
Is there any hope here, or am I stuck typing out many, many _, _, _, _?
EDIT: I understand that I can do case wl # WideLoad(...whatever...) => wl.d, yet I'm still wondering whether there's even terser syntax that does what I need without having to introduce an extra val.
I don't know if this is appropriate, but you can also build an object just to match that field, or that set of fields (untested code):
object WideLoadActorRef {
def unapply(wl: WideLoad): Option[ActorRef] = { Some(wl.d) }
}
someVal match {
case WideLoadActorRef(d) => d ! someMessage
}
or even
object WideLoadBnD {
def unapplySeq(wl: WideLoad): Option[(Int,ActorRef)] = { Some((wl.b,wl.d)) }
}
someVal match {
case WideLoadBnD(b, d) => d ! SomeMessage(b)
}
You can always fall back to guards. It's not really nice, but better than normal pattern matching for you monster case classes :-P
case class Foo(a:Int, b:Int, c:String, d:java.util.Date)
def f(foo:Foo) = foo match {
case fo:Foo if fo.c == "X" => println("found")
case _ => println("arrgh!")
}
f(Foo(1,2,"C",new java.util.Date())) //--> arrgh!
f(Foo(1,2,"X",new java.util.Date())) //--> found
That said I think you should rethink your design. Probably you can logically group some parameters together using case classes, tuples, lists, sets or maps. Scala does support nested pattern matching:
case class Bar(a: Int, b:String)
case class Baz(c:java.util.Date, d:String)
case class Foo(bar:Bar, baz:Baz)
def f(foo:Foo) = foo match {
case Foo(Bar(1,_),Baz(_,"X")) => println("found")
case _ => println("arrgh!")
}
f(Foo(Bar(1,"c"),Baz(new java.util.Date, "X"))) //--> found
f(Foo(Bar(1,"c"),Baz(new java.util.Date, "Y"))) //--> arrgh!
You can just specify the type in the matched pattern:
case class WideLoad(a: String, b: Int, c: Float, d: ActorRef, e: Date)
val someVal = WideLoad(...)
someVal match {
case w: WideLoad => w.d ! SomeMessage(...)
}
You can create a new case class which is a summary of your larger case class
case class WideLoad(a: String, b: Int, c: Float, d: ActorRef, e: Date)
case class WideLoadSummary(d: ActorRef, e: Date)
And then pattern match as normal.
val someVal = WideLoadSummary(wideload.d, wideload.e)
someVal match {
case WideLoadSummary(d, _) => d ! SomeMessage(...)
}
Related
Scala : Pattern matching with Option[Foo] and parameter of Foo
How can rewrite the following to make it more 'Scala way' or use just one match? case class Foo(bar: Any) val fooOpt = Some(Foo("bar as String")) def isValid(p: Any) = p match { case _ # (_: String | _: Int) => true case _ => false } //Is it possible to check for the type of bar directly in this if statement? fooOpt match { case Some(f) if isValid(f.bar) => doSomething case _ => doSomethingElse } One alternative would be using the isInstanceOf. fooOpt match { case Some(f) if f.bar.isInstanceOf[String] => doSomething case Some(f) if f.bar.isInstanceOf[Int] => doSomething //could also rewrite to use just one case case _ => doSomethingElse } Is there other way?
This can all be done in one big pattern match: fooOpt match { case Some(Foo(_: Int | _: String)) => doSomething case _ => doSomethingElse } If you want to get the Int or String out, just split that case: fooOpt match { case Some(Foo(i: Int)) => doSomething case Some(Foo(s: String)) => doSomething case _ => doSomethingElse }
Is there other way? Although the solution with one big patten match works(and can be used if you really can't change bar to anything more specific than Any), it is not a proper 'Scala way' of dealing with this situations in general if you have control over Foo. A better way would be to make Foo generic: case class Foo[T](bar: T) And have either a generic doSomething, if it can work with any particular T: def doSomething[T](foo: Foo[T]): SomeType = ??? or to have different versions of it for different possible T's you have, if it should react on them differently: def doSomethingWithString(foo: Foo[String]): SomeType = ??? def doSomethingWithInt(foo: Foo[Int]): SomeType = ??? Then you can use it just like this: val fooOpt = Some(Foo("bar as String")) fooOpt.map(doSomething).orElse(doSomethingElse) or like this: val fooOptString = Some(Foo("bar as String")) fooOptString.map(doSomethingWithString).orElse(doSomethingElse) val fooOptInt = Some(Foo(1)) fooOptInt.map(doSomethingWithInt).orElse(doSomethingElse) So, in this case compiler checks types for you, answering to: Is it possible to check for the type of bar directly? And in many situations you can avoid using pattern match at all, using methods like map, orElse, etc. with proper typing. This might be an answer for this: could also rewrite to use just one case
scala coding to do the Huffman decoding, but wrong result
abstract class CodeTree case class Fork(left: CodeTree, right: CodeTree, chars: List[Char], weight: Int) extends CodeTree case class Leaf(char: Char, weight: Int) extends CodeTree type Bit = Int def decode(tree: CodeTree, bits: List[Bit]): List[Char] = { if(!bits.isEmpty) { bits.head match { case 0 => tree match { case Fork(l, r, _, _) => decode(l, bits.tail) case Leaf(_, _) => chars(tree) ::: decode(frenchCode, bits.tail) } case 1 => tree match { case Fork(l, r, _, _) => decode(r, bits.tail) case Leaf(_, _) => chars(tree) ::: decode(frenchCode, bits.tail) } } } else Nil } val frenchCode: CodeTree = Fork(Fork(Fork(Leaf('s',121895),Fork(Leaf('d',56269),Fork(Fork(Fork(Leaf('x',5928),Leaf('j',8351),List('x','j'),14279),Leaf('f',16351),List('x','j','f'),30630),Fork(Fork(Fork(Fork(Leaf('z',2093),Fork(Leaf('k',745),Leaf('w',1747),List('k','w'),2492),List('z','k','w'),4585),Leaf('y',4725),List('z','k','w','y'),9310),Leaf('h',11298),List('z','k','w','y','h'),20608),Leaf('q',20889),List('z','k','w','y','h','q'),41497),List('x','j','f','z','k','w','y','h','q'),72127),List('d','x','j','f','z','k','w','y','h','q'),128396),List('s','d','x','j','f','z','k','w','y','h','q'),250291),Fork(Fork(Leaf('o',82762),Leaf('l',83668),List('o','l'),166430),Fork(Fork(Leaf('m',45521),Leaf('p',46335),List('m','p'),91856),Leaf('u',96785),List('m','p','u'),188641),List('o','l','m','p','u'),355071),List('s','d','x','j','f','z','k','w','y','h','q','o','l','m','p','u'),605362),Fork(Fork(Fork(Leaf('r',100500),Fork(Leaf('c',50003),Fork(Leaf('v',24975),Fork(Leaf('g',13288),Leaf('b',13822),List('g','b'),27110),List('v','g','b'),52085),List('c','v','g','b'),102088),List('r','c','v','g','b'),202588),Fork(Leaf('n',108812),Leaf('t',111103),List('n','t'),219915),List('r','c','v','g','b','n','t'),422503),Fork(Leaf('e',225947),Fork(Leaf('i',115465),Leaf('a',117110),List('i','a'),232575),List('e','i','a'),458522),List('r','c','v','g','b','n','t','e','i','a'),881025),List('s','d','x','j','f','z','k','w','y','h','q','o','l','m','p','u','r','c','v','g','b','n','t','e','i','a'),1486387) val secret: List[Bit] = List(0,0,1,1,1,0,1,0,1,1,1,0,0,1,1,0,1,0,0,1,1,0,1,0,1,1,0,0,1,1,1,1,1,0,1,0,1,1,0,0,0,0,1,0,1,1,1,0,0,1,0,0,1,0,0,0,1,0,0,0,1,0,1) def decodedSecret: List[Char] = decode(frenchCode, secret)ode here I am new to scala, and learning the pattern matching now, I want to do the huffman decoding, now I could get a list, but it is the wrong answer, hope someone could find the mistake.
There are several issues with your code. You do not want to consume a bit when you hit a leaf. A character of the leaf should also be added if there is no bit in your code. In the decode methode you do not want to reference frenchCode, but code instead that is given as a parameter. You can access the char of the leaf via pattern matching, i.e. case Leaf(codeChar, _) => ... Btw. your code will be way cleaner if you start matching on the tree. Only if it matches to a fork you look at the head of your bit list. Hope that helps. ;)
Overriding unapply method
I have a case from a library class and I want to override unapply method to reduce the number of parameters I need to pass to do pattern matching against it. I do this: object ws1 { // a library class case class MyClass(a: Int, b: String, c: String, d: Double /* and many more ones*/) // my object I created to override unapply of class MyClass object MyClass { def unapply(x: Int) = Some(x) } val a = new MyClass(1, "2", "3", 55.0 /* and many more ones*/) a match { case MyClass(x /*only the first one is vital*/) => x // java.io.Serializable = (1,2,3,55.0) case _ => "no" } } But I want it to return just 1. What's wrong with this?
case class MyClass(a: Int, b: String, c: String, d: Double /* and many more ones*/) object MyClassA { def unapply(x: MyClass) = Some(x.a) } val a = new MyClass(1, "2", "3", 55.0 /* and many more ones*/) a match { case MyClassA(2) => ??? // does not match case MyClassA(1) => a // matches case _ => ??? } You cannot define your custom unapply method in the MyClass object, because it would have to take a MyClass parameter, and there's already one such method there – one generated automatically for the case class. Therefore you have to define it in a different object (MyClassA in this case). Pattern matching in Scala takes your object and applies several unapply and unapplySeq methods to it until it gets Some with values that match the ones specified in the pattern. MyClassA(1) matches a if MyClassA.unapply(a) == Some(1). Note: if I wrote case m # MyClassA(1) =>, then the m variable would be of type MyClass. Edit: a match { case MyClassA(x) => x // x is an Int, equal to a.a case _ => ??? }
I would ditch the overloaded unapply and just use the following for the match: a match { case MyClass(x, _, _, _) => x // Result is: 1 case _ => "no" } EDIT : If you really want to avoid the extra underscores, I think you'll need to look at something like: a match { case x:MyClass => x.a case _ => "no" }
Scala matching, resolving the same variable from two different patterns
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.
Abstract away complexity of complex pattern match in scala
If I'm doing lots of pattern matching against a (relatively) complex case class, but most of the time I'm only interested in one or two of its fields. Is there a way to abstract away the other fields (perhaps by wrapping the class?)? Here's an example of the type of thing I'm trying to simplify: def receive = { case HttpRequest(POST, "foo", _, HttpBody(_, body), _) => // action case HttpRequest(GET, "bar", _, _, _) => // action } I'm only ever really interested in the request type, url and sometimes body so I would ideally like to define a pattern match as case Request(POST, "foo", body) or similar.
Just make your own Request extractor. Here's a simplified example: case class Complex(a: String, b: Int, c: String) object Simple { def unapply(c: Complex): Option[(String, Int)] = Some(c.a, c.b) } Complex("B", 2, "x") match { case Simple("A", i) => println("found A, " + i) case Simple("B", i) => println("found B, " + i) } // prints "found B, 2"