user3685285
user3685285

Reputation: 6586

Can a scala companion object have abstract members?

EDIT: To make the problem more clear

I have a function that tries to parse a Map[String, String] into ReadRequests and WriteRequests. I need the MapConvert to be implicitly accessible from the abstract class Request.

// Conversion methods to make conversions from Map[String, String] to ReadRequest and WriteRequest implicit

trait MapConvert[A] {
    def convert(values: Map[String, String]): A
}

object Map2ClassHelpers {
    implicit class Map2Class(values: Map[String, String]) {
        def extract[A](implicit mapper: MapConvert[A]): A = mapper convert values
    }
}

// abstract Request class

abstract class Request[R <: Request[R]]() {
    def companion: RequestCompanion[R]
}

trait RequestCompanion[R <: Request[R]] {
    implicit val m: MapConvert[R]
}

// ReadRequest and companion object

case class ReadRequest(source: String, data: String)
    extends Request[ReadRequest] {
    override def companion: RequestCompanion[ReadRequest] = ReadRequest
}

object ReadRequest extends RequestCompanion[ReadRequest] {
    implicit val m = new MapConvert[ReadRequest] {
        def convert(values: Map[String, String]) = ReadRequest(
            source = values("source"),
            data = values("data")
        )
    }
}

// WriteRequest and companion object

case class WriteRequest(destination: String, data: String)
    extends Request[WriteRequest] {
    override def companion: RequestCompanion[WriteRequest] = WriteRequest
}

object WriteRequest extends RequestCompanion[WriteRequest] {
    implicit val m = new MapConvert[WriteRequest] {
        def convert(values: Map[String, String]) = WriteRequest(
            destination = values("destination"),
            data = values("data")
        )
    }
}

case class Spec(requestType: String, args: Map[String, String])

def fromMapToRequest[R <: Request[R]](spec: Spec)(implicit mR: Manifest[R]): R = {
    /* this extract method comes from Map2ClassHelpers
     * the result should be either a ReadRequest or a WriteRequest
     * the error happens at this call saying "could not find implicit value for parameter mapper
     * my guess is because it can't implicitly find the correct MapConvert from the abstract Request class
     */

    // a bunch of checks ...
    spec.args.extract[R]
}

def runExample() = {
    val spec1: Spec = Spec("read", Map("source" -> "/readPath", "data" -> "ABC"))
    val spec2: Spec = Spec("write", Map("destination" -> "/writePath", "data" -> "ABC"))

    val specs = Seq(spec1, spec2)

    specs.map(spec => {
        spec.requestType match {
            case "read" => fromMapToRequest[ReadRequest](spec)
            case "write" => fromMapToRequest[WriteRequest](spec)
        }
    })
}

This works when I change it to:

def fromMapToRequest[R <: Request[R]](spec: Spec)(eval: spec => Request)(implicit mR: Manifest[R]): R = {
    // a bunch of checks ...
    import Map2ClassHelpers._
    eval(spec)
}

def runExample() = {
    val spec1: Spec = Spec("read", Map("source" -> "/readPath", "data" -> "ABC"))
    val spec2: Spec = Spec("write", Map("destination" -> "/writePath", "data" -> "ABC"))

    val specs = Seq(spec1, spec2)
    import Map2ClassHelpers._
    specs.map(spec => {
        // this part is kind of ugly compared to the first way
        spec.requestType match {
            case "read" => fromMapToRequest[ReadRequest](spec)(_.args.extract[ReadRequest])
            case "write" => fromMapToRequest[WriteRequest](spec)(_.args.extract[WriteRequest])
        }
    })
}

but I'd like the more generalized first option so only have to specify the type in fromMapToRequest once.

Upvotes: 0

Views: 298

Answers (2)

SergGr
SergGr

Reputation: 23788

I'm not sure if I understand your problem correctly, but if I do, the problem seems to be that you try to pass implicit Manifest[R] hoping that the compiler will be able to find the proper MapConvert[R] in the companion object and this approach doesn't work because Manifest does not extend the scope of the lookup for implicit parameters. But why can't you just pass MapConvert[R] itself as an implicit parameter into fromMapToRequest? When I change signature to

def fromMapToRequest[R <: Request[R]](spec: Spec)(implicit mc: MapConvert[R]): R = {

the code compiles and works as I would expect it to work (see it online here). If you need Manifest[R] for your other checks, just pass two implicit parameters.

Upvotes: 1

Andrey Tyukin
Andrey Tyukin

Reputation: 44918

(possibly outdated because of question edits)

Define a trait for the companion objects:

trait FoobarCompanion[X <: Foobar[X]] {
  implicit val m: Converter[X]
}

Let the companion objects extend it:

object Foo extends FoobarCompanion[Foo] {
  implicit val m = Converter[Foo] { ... }
}

object Bar extends FoobarCompanion[Bar] {
  implicit val m = Converter[Bar] { ... }
}

Add a method companion to the Foobar itself:

abstract class Foobar[F <: Foobar[F]] {
  def companion: FoobarCompanion[F]
}

Now you have access to the correct implicit whenever you get an instance of Foobar through the companion.

You might want to take a look at how the code around GenericCompanion is organized.

Upvotes: 1

Related Questions