Question

I'm trying to add an implicit value to (what I believe is) the companion object of a case class, but this implicit value is not found.

I'm trying to achieve something like the following:

package mypackage

object Main {
  def main(args: Array[String]): Unit = {
    val caseClassInstance = MyCaseClass("string")
    val out: DataOutput = ...
    serialize(out, caseClassInstance)
    // the above line makes the compiler complain that there is no
    // Serializer[MyCaseClass] in scope
  }
  def serialize[T : Serializer](out: DataOutput, t: T): Unit = {
    ...
  }
}
object MyCaseClass {
  // implicits aren't found here
  implicit val serializer: Serializer[MyCaseClase] = ...
}

case class MyCaseClass(s: String) {
  // some other methods
}

I've explicitly added the package here to show that both the MyCaseClass case class and object should be in scope. I know that the object is actually being constructed because I can get this to compile if I add

implicit val serializer = MyCaseClass.serializer

to main (though notably not if I add import MyCaseClass.serializer).

I'm concerned that the MyCaseClass object is not actually a companion of the case class, because if I explicitly define apply and unapply on the object and then attempt to call MyCaseClass.apply("string") in main, the compiler gives the following error:

ambiguous reference to overloaded definition,
both method apply in object MyCaseClass of type (s: String)mypackage.MyCaseClass
and  method apply in object MyCaseClass of type (s: String)mypackage.MyCaseClass
match argument types (String)
val a = InputRecord.apply("string")
                    ^

If it's not possible to take this approach, is there a way to use type classes with case classes without creating an implicit value every time it must be brought into scope?

EDIT: I'm using scala 2.10.3.

EDIT 2: Here's the example fleshed out:

package mypackage

import java.io.{DataOutput, DataOutputStream}

object Main {
  def main(args: Array[String]): Unit = {
    val caseClassInstance = MyCaseClass("string")
    val out: DataOutput = new DataOutputStream(System.out)
    serialize(out, caseClassInstance)
    // the above line makes the compiler complain that there is no
    // Serializer[MyCaseClass] in scope
  }
  def serialize[T : Serializer](out: DataOutput, t: T): Unit = {
    implicitly[Serializer[T]].write(out, t)
  }
}
object MyCaseClass {
  // implicits aren't found here
  implicit val serializer: Serializer[MyCaseClass] = new Serializer[MyCaseClass] {
    override def write(out: DataOutput, t: MyCaseClass): Unit = {
      out.writeUTF(t.s)
    }
  }
}

case class MyCaseClass(s: String) {
  // some other methods
}

trait Serializer[T] {
  def write(out: DataOutput, t: T): Unit
}

This actually compiles, though. I am getting this issue when using Scoobi's WireFormat[T] instead of Serializer, but can't provide a concise, runnable example due to complexity and the Scoobi dependency. I will try to create a more relevant example, but it seems as though the issue is not as general as I thought.

Was it helpful?

Solution

It turns out that the type class instances actually need to be implicit values, rather than objects. The MyCaseClass object above works because its serializer is assigned to an implicit value. However, this implementation

object MyCaseClass {
  implicit object MyCaseClassSerializer extends Serializer[MyCaseClass] {
    override def write(out: DataOutput, t: MyCaseClass): Unit = {
      out.writeUTF(t.s)
    }
  }
}

fails with the error

Main.scala:9: error: could not find implicit value for evidence parameter of type mypackage.Serializer[mypackage.MyCaseClass]
    serialize(out, caseClassInstance)
             ^ 

In my real code, I was using an auxiliary function to generate the Serializer[T] (see https://github.com/NICTA/scoobi/blob/24f48008b193f4e87b9ec04d5c8736ce0725d006/src/main/scala/com/nicta/scoobi/core/WireFormat.scala#L137). Despite the function having its own explicit return type, the type of the assigned value was not being inferred correctly by the compiler.

Below is the full example from the question with such a Serializer-generator.

package mypackage

import java.io.{DataOutput, DataOutputStream}

object Main {
  import Serializer._
  def main(args: Array[String]): Unit = {
    val caseClassInstance = MyCaseClass("string")
    val out: DataOutput = new DataOutputStream(System.out)
    serialize(out, caseClassInstance)
  }
  def serialize[T : Serializer](out: DataOutput, t: T): Unit = {
    implicitly[Serializer[T]].write(out, t)
  }
}

object MyCaseClass {
  import Serializer._
  // does not compile without Serializer[MyCaseClass] type annotation
  implicit val serializer: Serializer[MyCaseClass] = 
      mkCaseSerializer(MyCaseClass.apply _, MyCaseClass.unapply _)
}

case class MyCaseClass(s: String)

trait Serializer[T] {
  def write(out: DataOutput, t: T): Unit
}

object Serializer {
  // does not compile without Serializer[String] type annotation
  implicit val stringSerializer: Serializer[String] = new Serializer[String] {
    override def write(out: DataOutput, s: String): Unit = {
      out.writeUTF(s)
    }
  }

  class CaseClassSerializer[T, A : Serializer](
      apply: A => T, unapply: T => Option[A]) extends Serializer[T] {
    override def write(out: DataOutput, t: T): Unit = {
      implicitly[Serializer[A]].write(out, unapply(t).get)
    }
  }

  def mkCaseSerializer[T, A : Serializer]
      (apply: A => T, unapply: T => Option[A]): Serializer[T] =
    new CaseClassSerializer(apply, unapply)
}

OTHER TIPS

This related, simple code below prints 1.

object A{
  implicit def A2Int(a:A)=a.i1
}
case class A(i1:Int,i2:Int)

object Run extends App{
  val a=A(1,2)
  val i:Int=a
  println(i)
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top