Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scala Builder Pattern: illegal cyclic reference involving type T

I'm trying to write some generic builders for my User class hierarchy. I have a trait, UserBuilder and each "with" method in the trait has to return the same type as the current class. So if I'm inside the ComplexUserBuilder, the withId method should return a ComplexUserBuilder and not a UserBuilder.

But I'm getting

illegal cyclic reference involving type T

Is there a way to workaround this?

Here is my code:

trait UserBuilder[T >: UserBuilder[T]] {

  var id: String = ""

  def withId(id: String): T = {
    this.id = id
    return this
  }
}

class ComplexUserBuilder extends UserBuilder[ComplexUserBuilder] {

  var username: String = ""

  def withUsername(username: String): ComplexUserBuilder = {
    this.username = username
    return this
  }

  def build = new ComplexUser(id, username)
}

By the way, if I replace trait UserBuilder[T >: UserBuilder[T]] with trait UserBuilder[T >: UserBuilder[_]] I get:

type arguments [model.ComplexUserBuilder] do not conform to trait UserBuilder's type parameter bounds [T >: model.UserBuilder[_]]

Update:

trait UserBuilder[T >: UserBuilder[T]]

should be (as GClaramunt suggested)

trait UserBuilder[T <: UserBuilder[T]]

but now there is an ugly cast as the return type

like image 920
Danix Avatar asked Jun 11 '14 22:06

Danix


3 Answers

To make your UserBuilder subclasses have a reference to their own type, you should declare the type of this (and make them sub-types, not super-types, of UserBuilder):

trait UserBuilder[T <: UserBuilder[T]] { this: T =>
  ...
}
like image 172
Dan Getz Avatar answered Oct 20 '22 08:10

Dan Getz


Dan has solved your problem pretty clear. What I want to say is, you even don't need the generic T at all, by using the this.type feature of scala.

trait UserBuilder {

  var id: String = ""

  def withId(id: String): this.type = {
    this.id = id
    return this
  }
}

class ComplexUserBuilder extends UserBuilder {

  var username: String = ""

  def withUsername(username: String): this.type = {
    this.username = username
    return this
  }

  def build = new ComplexUser(id, username)
}

And, if you want to add the build method to the UserBuilder trait, you may want to add a generic type U to constraint the user type, like

trait UserBuilder[U <: { def id: String }] {

  var id: String = ""

  def withId(id: String): this.type = {
    this.id = id
    return this
  }

  def build: U
}

class ComplexUserBuilder extends UserBuilder[ComplexUser] {

  var username: String = ""

  def withUsername(username: String): this.type = {
    this.username = username
    return this
  }

  def build = new ComplexUser(id, username)
}
like image 41
Shiva Wu Avatar answered Oct 20 '22 07:10

Shiva Wu


I'm not sure what are you trying to achieve, but you have the upper type bound restriction backwards should be "<:" instead of ":>"

:> is a lower type bound restriction, meaning, T must be a super type of UserBuilder, you want a subtype (so you can extend UserBuilder )

trait UserBuilder[T <: UserBuilder[T]] {
    self: T =>

    var id: String = ""

    def withId(id: String): T = {
      this.id = id
      this
    }
  }

  class ComplexUserBuilder extends UserBuilder[ComplexUserBuilder] {

    var username: String = ""

    def withUsername(username: String): ComplexUserBuilder = {
      this.username = username
      this
    }

    //def build = new ComplexUser(id, username)
  }

Also, you don't need "return", (almost) everything in Scala is an expression that returns a value.

like image 1
GClaramunt Avatar answered Oct 20 '22 06:10

GClaramunt