What is the correct way to implement trait with generics in Scala?
Asked Answered
B

1

7

I have some simple traits (Entity in the example below) that are extended by case classes in my app. I would like to create an EntityMapper trait that provides an interface for handling the case classes that extend the Entity trait (Foo in the example below). I thought I should be able to do this fairly easily using generics and bounding but I've spent a couple of hours on it already and I haven't gotten it to work correctly. The code below is what I think I should be able to do but it fails with a compiler error. The error is

Test.scala:15: error: value id is not a member of type parameter Foo \ println(e.id)

package experiment

trait Entity {
    val id: Option[Long]
}

case class Foo(val id: Option[Long] = None) extends Entity

trait EntityMapper {
    def create[E <: Entity](e: E): E
}

object FooMapper extends EntityMapper {
    def create[Foo](e: Foo): Foo = {
        println(e.id)
        e
    }
}

object Main extends App {
    val foo = FooMapper.create(Foo(None))
}

I've tried several different things to solve the problem but nothing has worked. If I comment out the line in question "println(e.id)", it compiles but that is not useful because I cannot access or modify any of the properties of Foo.

I have tried using a covariant argument to the mapper trait and then supplying the type to the FooMapper object definition but that yields the same error. The code for that attempt is below:

trait EntityMapper[+Entity] {
    def create[E <: Entity](e: E): E
}

object FooMapper extends EntityMapper[Foo] {
...
}

I have also tried achieving the same thing with simple inheritance but I cannot correctly restrict the type parameter in FooMapper to only take Foos, I have to make the method signature match the trait exactly which is why I started trying to implement it using generics with a type bound. The code for that attempt is below:

trait EntityMapper {
    def create(e: Entity): Entity
}

object FooMapper extends EntityMapper {
    def create(e: Foo): Foo = {
        println(e.id)
        e
    }
}

The error code returned is:

Test.scala:13: error: object creation impossible, since method create in trait EntityMapper of type (e: experiment.Entity)experiment.Entity is not defined

(Note that experiment.Entity does not match experiment.Foo: class Foo in package experiment is a subclass of trait Entity in package experiment, but method parameter types must match exactly.)

object FooMapper extends EntityMapper {
       ^

Any help would be greatly appreciated. I'm using Scala version 2.10.3.

Bernardina answered 3/9, 2014 at 17:24 Comment(0)
R
10

You can fix the error in a couple of ways

1.Specifying the generic type constraint on the trait.

trait EntityMapper[E <: Entity] {
  def create(e: E): E
}

object FooMapper extends EntityMapper[Foo] {
  def create(e: Foo): Foo = {
    println(e.id)
    e
  }
}

2.Use parameterized types

trait EntityMapper {
  type E <: Entity
  def create(e: E): E
}

object FooMapper extends EntityMapper {
  type E = Foo
  def create(e: Foo): Foo = {
    println(e.id)
    e
  }
}

Look at Scala: Abstract types vs generics to get some more background on the two approaches.

Rebirth answered 3/9, 2014 at 18:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.