chrsan
chrsan

Reputation: 3400

Extending a trait and types

I would like to have a sealed trait which have a declared method that returns the actual class that extends the trait. Should I use an abstract type, a parameter type or is there any other nice way to solve this?

sealed trait Foo {
  type T
  def doit(other: T): T
}

or

sealed trait Foo[T] {
  def doit(other: T): T
}

Note that T must be a subtype of Foo in this example. If I do it like this the type information feels too repeated:

case class Bar(name: String) extends Foo[Bar] {
  def doit(other: Bar): Bar = ...
}

Upvotes: 9

Views: 5282

Answers (5)

Aaron Novstrup
Aaron Novstrup

Reputation: 21017

You can cut down on the repetition somewhat by having your doit method return a factory function:

trait Foo[T] { 
   self: T =>
   def doit: T => T 
}

case class Bar(name: String) extends Foo[Bar] {
   // note: types omitted 
   def doit = { other => Bar(name + other.name) }
}

It's not possible to do the same with an abstract type:

trait Foo { 
   self: T => // won't compile because T isn't defined yet
   type T 
   def doit: T => T
}

Upvotes: 2

Landei
Landei

Reputation: 54584

You can write:

trait Foo[T] {
  self:T =>
  def doit(other: T): T
}

case class Bar(name: String) extends Foo[Bar] {
  def doit(other: Bar): Bar = ...
}

The difference to your example is that Bar can't be instantiated in any other way (e.g. case class Bar(name: String) extends Foo[String]).

Upvotes: 2

oxbow_lakes
oxbow_lakes

Reputation: 134340

EDIT - Below is my original answer. Your comment indicates that you wish to return an arbitrary instance of a matching type but I don't really believe that this is in any way sensible. Suppose it were, via the T.type syntax:

trait T { def foo : T.type }

trait U extends T { def foo = new U } //must be a U

class W extends U

val w : W = (new W).foo //oh dear.

This is accomplishable via this.type:

scala> trait T {
 | def foo : this.type
 | }
defined trait T

scala> class W extends T {
 | def foo  = this
 | }
defined class W

scala> (new W).foo
res0: W = W@22652552

scala> res0.foo
res1: res0.type = W@22652552

And then also:

scala> ((new W) : T)
res4: T = W@45ea414e

scala> res4.foo.foo.foo
res5: res4.type = W@45ea414e

Upvotes: 1

Kim Stebel
Kim Stebel

Reputation: 42045

trait Foo[A <: Foo[A]]

This trait can only be mixed in if A is a subtype of Foo[A] and the only type satisfying that is the class Foo is being mixed into. I saw this solution in the Mapper traits in Lift.

Upvotes: 1

IttayD
IttayD

Reputation: 29163

They are mostly interchangeable. According to Odersky, the reason was mainly for completeness: That similarly to the fact that methods and fields (values) can be either abstract or passed as parameters, so can types.

It is better to use an abstract type when you intend to mix several traits that all use the same type name. With type parameters you need to explicitly pass the type to each

Here's an article explaining all of this: http://www.artima.com/weblogs/viewpost.jsp?thread=270195

Upvotes: 4

Related Questions