19
votes

I tried to combine implicit arguments with case classes, but I got stuck.

case class C(i: Int)(implicit b: Boolean)
val c1 = C(1)(true)
implicit val b = true
val c2 = C(2)
c1 match {
  case C(i)(b) => // doesn´t work
  case C(i,b)  => // doesn´t work
  case C(i)    => // works, but wanted: if (b) i else 0 
}

According to the Scala Language Specification it´s due to the compiler generated extractor object for case classes: My implicit Boolean is not a member of the resulting case class, so it has to be in the second (implicit) argument list (that I can´t find in the companion object´s apply method, unfortunately):

A case class definition of c[tps](ps1 ). . .(psn) with type parameters tps and value parameters ps implicitly generates an extractor object (§8.1.8) which is defined as follows:

object c {
  def apply[tps](ps1 ). . .(psn): c[tps] = new c[Ts](xs1 ). . .(xsn)
  def unapply[tps](x: c[tps]) =
    if (x eq null) scala.None
    else scala.Some(x.xs11, . . . , x.xs1k)
}

How can I define a case class with members that are implicitly supplied at creation time?

2

2 Answers

24
votes

You can define a case class with implicit arguments, but as you've discovered, they're not available for pattern matching. You could always write your own extractor though:

case class C(i: Int)(implicit val b: Boolean)

// You can't call this C because that seat is taken (can't overload the default unapply)
object C_ { 
  // In order to be able to select `b` here, 
  // it needs to be declared as "implicit *val* b: Boolean"
  def unapply(in: C) = Some((in.i, in.b)) 
}

c1 match {
  case C_(i, b) => ...
}
6
votes

Alex's answer is clever, however I don't really like the _ in the object name, I find the syntax a little strange and remembering the underscore makes pattern matching more difficult to use. (Of course this is all subjective so your feelings may vary).

My first approach to dealing with this were to move the implicit parameters to the apply method in the companion object.

case class A(i: Int, b: Boolean)

object Foo {
  def apply(i: Int)(implicit b: Boolean): Foo = apply(a, b)
}

But this results in

Error:(21, 14) double definition: 
method apply:(i: Int, b: Boolean)com.workday.cloud.master.package.A and 
method apply:(i: Int)(implicit b: Boolean)com.workday.cloud.master.package.A at line 24 
have same type after erasure: (i: Int, b: Boolean)com.workday.cloud.master.package.A   
  case class A(i: Int, b: Boolean)
             ^

We can fix this by adding an extra unused implicit parameter.

object A {
  def apply(i: Int)(implicit b: Boolean, unused: DummyImplicit): A = apply(i, b)
}

Which approach you choose is up to you, I find this approach enables my client code to look more natural.