mipadi
mipadi

Reputation: 410592

Can a parameterized trait specify that one of its abstract classes return a value of the same type as implementing classes?

Say I have a trait like this:

trait BaseTrait[A] {
    def someMethod[B](f: A => T[B]): T[B]
}

I want T to basically be "the same type as the class that implements this method". In other words, I want to be able to write this:

class ConcreteClass[A] extends BaseTrait[A] {
    def someMethod[B](f: A => ConcreteClass[B]): ConcreteClass[B]
}

And have that method satisfy the trait's requirement for someMethod. Is that possible?

Upvotes: 1

Views: 523

Answers (3)

Josh Lemer
Josh Lemer

Reputation: 446

What you are asking about is called F-bound types, and this is the best explanation I've seen on the topic: https://tpolecat.github.io/2015/04/29/f-bounds.html

The punch line is that you'll either want to switch to using typeclasses rather than inheritance, or you'll have to settle for getting almost all the way to what you want, like this:

trait BaseTrait[A <: BaseTrait[A]] { this: A => 
  def someMethod[B](f: A => T[B]): T[B]
}

class Concrete extends BaseTrait[Concrete]{  
  def someMethod[B](f: A => T[B]): T[B]
}

Now in your case your trait itself takes a type parameter, so you'll also have to work in an other type parameter, and use higher-kinded types like so:

trait BaseTrait[A, T[X] <: BaseTrait[X, T]] { this: T[A] =>
  def someMethod[B](f: A => T[B]): T[B]
}

class Concrete[A] extends BaseTrait[A, Concrete]{
  def someMethod[B](f: A => Concrete[B]): Concrete[B] = ???
}
//class DoesNotCompile[A] extends BaseTrait[Concrete, A]{
//  def someMethod[B](f: A => Concrete[B]): Concrete[B] = ???
//}

//class DoesNotCompile2[A] extends BaseTrait[DoesNotCompile2, A]{
//  def someMethod[B](f: A => Concrete[B]): Concrete[B] = ???
//}

object Ex {
  val concrete: Concrete[List[Int]] = new Concrete[List[Int]]
  val concrete2: Concrete[String] = concrete.someMethod(_ => new Concrete[String])

}

Upvotes: 8

Joe K
Joe K

Reputation: 18424

Here's a version using a type member instead of a type parameter. I think it's down to personal preference which you prefer.

trait BaseTrait[A] {
  type Self[X] <: Base[X]
  def doSomething[B](f: A => Self[B]): Self[B]
}

class ConcreteClass[A] extends BaseTrait[A] {
  type Self[X] = ConcreteClass[X]
  def doSomething[B](f: A => ConcreteClass[B]): ConcreteClass[B] = ???
}

It wouldn't stop some concrete implementation from defining Self to be something other than itself, but I don't know of any solution to that (it's a common problem).

Upvotes: 1

Mateusz Kubuszok
Mateusz Kubuszok

Reputation: 27535

Maybe something like?:

trait BaseTrait[M[_], A] { self: M[A] =>
  def someMethod[B](f: A => M[B]): M[B]
}

class ConcreteClass[A] extends BaseTrait[ConcreteClass, A] {
  def someMethod[B](f: A => ConcreteClass[B]): ConcreteClass[B] = ???
}

I think it would be difficult to get it more concrete than that.

Upvotes: 3

Related Questions