Greg
Greg

Reputation: 11512

Scala Type Parameter and Case Classes

I have a function like this:

private def fixBrand[T]( item:T ) = item.copy(brand = item.brand.toLowerCase)

This isn't compiling---complains T doesn't have function copy. Now I know that every item I'll pass in is a case class. How can I tell the compiler? Each of these case classes also have a 'brand' field, but are unrelated, in terms of object hierarchy.

I thought I read that Scala 2.10 had a feature that allowed me to use T as "a thing" that had function x, y, ...? Forgot what that feature was called, though!

Upvotes: 4

Views: 5096

Answers (2)

Vladimir Matveev
Vladimir Matveev

Reputation: 127801

From your function alone it is not clear at all that T has copy method, not to mention that this method also should have brand as one of the parameters.

I think the closest thing you can get is the usage of view bounds, something like

trait WithBrandCopyable[T] {
  def copy(brand: String): T
  def brand: String
}

object Classes {
  case class Class1(brand: String, number: Int)
  case class Class2(factor: Double, brand: String)

  implicit def class1ToWithBrandCopyable(obj: Class1) = new WithBrandCopyable[Class1] {
    def copy(brand: String) = obj.copy(brand = brand)
    def brand = obj.brand
  }

  implicit def class2ToWithBrandCopyable(obj: Class2) = new WithBrandCopyable[Class2] {
    def copy(brand: String) = obj.copy(brand = brand)
    def brand = obj.brand
  }
}

object Main {
  def fixBrand[T <% WithBrandCopyable[T]](obj: T) =
    obj.copy(brand = obj.brand.toLowerCase)

  def main(args: Array[String]) {
    import Classes._  // Import both classes and implicit conversions

    val obj1 = Class1("BrAnD1", 10)
    val obj2 = Class2(0.3, "BRAnd2")

    println(fixBrand(obj1))  // Prints Class1("brand1",10)
    println(fixBrand(obj2))  // Prints Class2(0.3,"brand2")
  }
}

Of course, variations are possible (e.g. use of full-fledged type classes), but all of them will include implicits in some form.

Note that it may be possible to make WithBrandCopyable a supertrait for each of case classes and then use plain upper bound for the generic parameter. I have used implicit conversions instead for two reasons. First, because I wanted to keep copy method. It won't be possible to define copy(brand: String) method in some trait and then inherit this trait by your case class because it would clash with compiler-generated copy(<case class arguments>) method, as Mark has mentioned in his comment to the other answer. Second, implicit conversions do not require you to modify your case classes at all - writing conversion methods is sufficient.

However, this approach requires some boilerplate, namely, an interface trait and an implicit conversion for each of your case classes to this trait. It may be possible to reduce this boilerplate by the use of macros, but not that much, I think.

This boilerplate really has a reason. obj.copy(brand = brand) method call may look the same in the source code, but really it is a call to two completely different methods: first has signature like (String, Int) => Class1 and second has signature like (Double, String) => Class2, not to mention that they belong to different classes which do not have common ancestor. Of course, byte code for these calls will be different. Macros would help here, but as far as I know, they still are not powerful enough to reduce this boilerplate completely, at least, in current stable version of Scala.

Upvotes: 2

Lee
Lee

Reputation: 144136

You could use structural typing:

def fixBrand[T <: { def copy(t: String): T; def brand: String }](item: T) = item.copy(item.brand.toLowerCase)

Upvotes: 4

Related Questions