Dylan
Dylan

Reputation: 13922

Define a common trait for types with different numbers of type parameters

Suppose I have two traits:

trait Generic1[T] {
  def mapR[U](f: Result[T] => Result[U]): Generic1[U]
}

trait Generic2[A, T] {
  // pretty much the same as Generic1, but with the extra `A` type param
  def mapR[U](f: Result[T] => Result[U]): Generic2[A, U]
}

Is there a way that I can define a single trait that defines some common functionality based on the mapR method?

// for example
trait MapR[T, ???] {
  def mapR[U](f: Result[T] => Result[U]): ???[U]

  def map[U](f: T => U): ???[U] = mapR(_ map f)
  def flatMap[U](f: T => Result[U]): ???[U] = mapR(_ flatMap f)
  def withFilter(f: T => Boolean): ???[T] = mapR(_ withFilter f)
}

How would the ??? be defined in the MapR trait above so that I could instead define

trait Generic1[T] extends MapR[T, ???]
trait Generic2[A, T] extends MapR[T, ???]

extra issues

@m-z's answer handles the question asked above, but I run into some further troubles when the GenericN traits have type variance:

trait Generic1[+T] extends MapR[T] {
  type R[X] = Generic1[X]
  ...
}
trait Generic2[-A, +T] extends MapR[T] {
  type R[X] = Generic2[A, X]
  ...
}

I get errors like "contravariant type A occurs in invariant position in type [X]Generic2[A, X] of type R"

I tried redefining R to be covariant, but it still doesn't touch the A type and I get similar errors.

Can the approach in the answer be adapted to handle the type variance described above?

Upvotes: 0

Views: 71

Answers (2)

Michael Zajac
Michael Zajac

Reputation: 55569

You can make the MapR trait have a type member with a single type parameter than can alias the Generic traits on implementation. This would require each Generic trait to describe how it is parameterized.

trait Result[T]

trait MapR[T] {

  type R[U]

  def mapR[U](f: Result[T] => Result[U]): R[U]

}

trait Generic1[T] extends MapR[T] {
  type R[U] = Generic1[U]
}

trait Generic2[A, T] extends MapR[T] {
  type R[U] = Generic2[A, U]
}

Upvotes: 2

Norwæ
Norwæ

Reputation: 1575

You could probably achieve this by declaring the supertrait as higher-kinded. Something along these lines might be possible:

trait Result[T]

trait MapRSupport[SelfType[X], T] {
  def mapR[U](f: Result[T] => Result[U]): SelfType[U]
}

trait Generic1[T] extends MapRSupport[Generic1, T]{
  def mapR[U](f: Result[T] => Result[U]): Generic1[U] = ???
}

trait Generic2[A] extends MapRSupport[Generic2, A]{
  type T
  // pretty much the same as Generic1, but with the extra `A` type param
  def mapR[U](f: Result[T] => Result[U]): Generic2[A] { type T = U} = ???
}

Upvotes: 1

Related Questions