Search code examples
scalatypeclasstype-erasurehigher-kinded-types

Typeclass of a polymorphic type suffers from type erasure


Given a polymorphic type Distribution, I want to establish, what I would call a typeclass on this (polymorphic) type. I think this goes under the term Higer Kinded Types.

trait Distribution[A] extends Measure[A] with Random[A] {
  type Domain = A
}

Distributions instantiating this typeclass should implement a method train, which given a List of elements in the domain of the distribution, return an object this distribution.

trait ML[D <: Distribution[_] ] {
  def train( samples: List[D#Domain] ): D
}

But when I try to instantiate like this (Binomial is a Distribution[Int]):

implicit object MLBinomial extends ML[Binomial] {
  def train( samples: List[Int] ) : Binomial = ???
}

I get the following error:

Binomial.scala:181: object creation impossible, since method train in trait ML
of type (samples: List[_$1])org.lanyard.dist.disc.Binomial is not defined
(Note that List[_$1] does not match List[Int]: their type parameters differ)

I am guessing I am the victim of type erasure but I am not sure and just learning to fight with the scala type system. Is anybody able to fix this code or am I forced to use a different design?

Thank you in advance.


Solution

  • When you're defining

    trait ML[D <: Distribution[_]] {
      def train(samples: List[D#Domain]): D
    }
    

    then D#Domain cannot be fixed because you're saying that D is a subtype of Distribution[_] where you really don't care about what _ actually is. If instead you want to infer the inner type, you could do as follows:

    trait Measure[T]
    trait Random[T]
    
    trait Distribution[A] extends Measure[A] with Random[A] {
      type Domain = A
    }
    
    trait Binomial extends Distribution[Int]
    
    trait ML[A, D <: Distribution[A]] {
      def train(samples: List[A]): D    // could be List[D#Domain] now as well
    }
    
    object ML {
      implicit object MLBinomial extends ML[Int, Binomial] {
        def train(samples: List[Int]) : Binomial = ???
      }
    }
    

    EDIT: If you don't want to mess around with multiple type parameters, you could also restrict Domain to a subtype of A. This will type-check and (hopefully) give you what you want. You'd only have to specify Domain when defining Binomial:

    trait Distribution[A] extends Measure[A] with Random[A] {
      type Domain <: A
    }
    
    trait Binomial extends Distribution[Int] { type Domain = Int }
    
    trait ML[D <: Distribution[_]] {
      def train(samples: List[D#Domain]): D
    }
    
    object ML {
      implicit object MLBinomial extends ML[Binomial] {
        def train(samples: List[Int]): Binomial = ???
      }
    }