Nathan Merrill
Nathan Merrill

Reputation: 8396

How to extract type parameter from type

Given the following classes:

abstract class Foo[B] 
abstract class Baz[B, F <: Foo[B]] {
    def get(foo: F): B
    // other methods
} 

I hate that I need two type parameters in Baz when the first is redundant. I want to write something like:

abstract class Baz[F <: Foo[B]] {
  def get(foo: F): B
}

Is it possible for me to reference the B type (of F) within Baz without taking multiple type parameters? This feels like it should be possible, but I can't seem to figure out the syntax.

Upvotes: 2

Views: 283

Answers (2)

Dmytro Mitin
Dmytro Mitin

Reputation: 51713

  1. Can you make B a type member rather than type parameter?

    abstract class Foo { type B }
    abstract class Baz[F <: Foo] {
      def get(foo: F): F#B
      // other methods
    }
    

    Then if you need both type parameter and type member you can use Aux-pattern

    abstract class Foo { type B }
    // Foo.Aux[B] instead of Foo[B]
    object Foo {
      type Aux[B0] = Foo { type B = B0 }
    }
    abstract class Baz[F <: Foo] {
      def get(foo: F): F#B
      // other methods
    }
    
  2. Can you make F higher-kinded and get polymorphic? (Looks like sort of "tagless final" approach.)

    abstract class Foo[B]
    abstract class Baz[F[X] <: Foo[X]] {
      def get[B](foo: F[B]): B
      // other methods
    }
    
  3. Can you make Foo a type class?

    abstract class Foo[F] {
      type B
    }
    object Foo {
      type Aux[F, B0] = Foo[F] { type B = B0 }
      def instance[F, B0]: Aux[F, B0] = new Foo[F] { type B = B0 }
    
      //instead of  class F1 extends Foo[B1]
      implicit val foo1: Aux[F1, B1] = instance
    }
    
    abstract class Baz[F](implicit val foo: Foo[F]) {
      def get: foo.B
      // other methods
    }
    

    or

    abstract class Baz[F: Foo] {
      val foo: Foo[F] = implicitly
      def get: foo.B
      // other methods
    }
    
  4. Can you extract the two type parameters to a new class?

    abstract class Foo[B]
    
    abstract class Tuple {
      type B
      type F <: Foo[B]
    }
    
    abstract class Baz[T <: Tuple] {
      def get(foo: T#F): T#B
      // other methods
    }
    

    or

    abstract class Baz[T <: Tuple](t: T) {
      def get(foo: t.F): t.B
      // other methods
    }
    

Upvotes: 6

jwvh
jwvh

Reputation: 51271

I hate that I need two type parameters in Baz when the first is redundant.

There is no redundancy. If the Baz code makes reference to 2 different types then we need to have 2 different names for them (F and B, or X and Y, or THIS and THAT, it doesn't really matter).

If the Baz code only makes reference to 1 type, but that type needs to be restricted to something that Foo has implemented then you could add that restriction:

class Baz[X](implicit ev: Foo[X]) { ...

This can be simplified as:

class Baz[X: Foo] { ...

If the types cannot be inferred, and you wish to simplify instance creation, perhaps a type member/alias can be used.

abstract class Baz[B] {
  type F = Foo[B]
  def get(foo: F): B
}

But it's often clearer just to write things out.

abstract class Baz[B] {
  def get(foo: Foo[B]): B
}

But this, of course, eliminates the possibility of sub-types.

Upvotes: 2

Related Questions