45

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.

Max A.
  • 4,842
  • 6
  • 29
  • 27
  • Even with 4 or 5 fields, all the underscores make it pretty hard to read. A named parameter syntax would improve readability a lot, but as far as I know nothing like this exists yet. – Aaron Novstrup Aug 13 '10 at 06:14
  • I was under the impression that long parameter lists are something to avoid in general. –  Aug 13 '10 at 07:41
  • you meant case WideLoad (d == dActor) – user unknown Aug 14 '10 at 03:01
  • 4
    There's a SIP about this very issue: https://issues.scala-lang.org/browse/SI-6524 – James Moore Nov 01 '14 at 18:29
  • @JamesMoore Regarding the issue, I wonder whether inspiration can be taken from import statement syntax. We have that `import org.apache.{foo,bar=>bat}` imports foo and bar, renaming the latter to bat. In the same way we could do `case WideLoad{d=>actorRef} if actorRef == 42 => SomeMessage(...)` – Max Murphy Mar 29 '18 at 12:20

4 Answers4

39

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)
}
Magnus
  • 1,152
  • 8
  • 20
  • 1
    I like your idea a lot. It does exactly what I need, with little extra syntax, and explicitly defined intent, type safety, etc. It's a great stop gap solution until such time when I've refactored this code to have smaller case classes. – Max A. Aug 13 '10 at 12:06
17

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! 
Landei
  • 54,104
  • 13
  • 100
  • 195
  • Yes, I am refactoring towards that goal. It may be easier to do this than build sand castles akin to what I proposed in my question. – Max A. Aug 13 '10 at 12:05
1

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(...)
}
vaer-k
  • 10,923
  • 11
  • 42
  • 59
1

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(...)
}
Ian Purton
  • 15,331
  • 2
  • 27
  • 26