Вопрос

Don't be put off by the long text, the points are quite trivial but require a bit of code to illustrate the problem. :-)

The Setup:

Say I would like to create a trait, here modeled as a Converter of some kind, that itself is generic but has a typed method convert() that returns an appropriately typed result object, say a Container[T]:

 trait Converter {
   def convert[T]( input: T ) : Container[T]
 } 
 trait Container[T]  // details don't matter

My question is about type constraints on methods, in particular for enforcing equality, and has two closely related parts.

Part 1: Say now that there was a specialized container type that was particularly suitable for array-based contents, like so:

 object Container {
   trait ForArrays[U] extends Container[Array[U]] 
 }

Given this possibility, I'd now like to specialize the Converter and in particular the return type of the convert() method, to the specialized Container.ForArrays type:

 object Converter {
   trait ForArrays extends Converter {
     // the following line is rubbish - how to do this right?
     def convert[E,T <: Array[E]]( input: T ) : Container.ForArrays[E]
   } 
 }

So that I can do something like this:

val converter = new Converter.ForArrays { ... }
val input = Array( 'A', 'B', 'C' )   
val converted : Container.ForArrays[Char] = converter.convert( input )

Basically I want Scala, if the type of converter is known to be Converter.ForArrays, to also infer the specialized return type of convert[Char]() as Container.ForArrays[Char], i.e. the matching container type plus the array type of the input. Is this or something like it possible and if so, how do I do it? E.g. how do I specify the type parameters / bounds on convert() (what is provided is just a stand-in - I have no idea how to do this). Oh, and naturally so that it still overrides its super method, otherwise nothing is gained.

Part 2: As a fallback, should this not be possible, I could of course push the convert function down into the Array-focused variant, like so:

 trait Converter   // now pretty useless as a shared trait
 object Converter {
   trait ForValues extends Converter { 
     def convert[T]( input: T ) : Container[T] 
   }
   trait ForArrays extends Converter {
     def convert[E]( input: Array[E] ) : Container.ForArrays[E]
   } 
 }

OK. Now say I have an even more specialized Converter.ForArrays.SetBased that can internally use a set of elements of type E (the same as the 'input' array element type) to do some particular magic during the conversion. The set is now a parameter of the trait, however, like so:

 case class SetBased( set: Set[F] ) extends Converter.ForArrays {
   // the following line is also rubbish...
   def convert[E = F]( input: Array[E] ) : Container.ForArrays[E] = {...}
 }

Again, this is about the type parameters of the convert() method. The difficulty here is: how do I glue the type parameter of the class - F - to the type parameter of the method - E - such that the Scala compiler will only let the user call convert() with an array whose elements match the elements of the set? Example:

val set = Set( 'X', 'Y', 'Z' )
val converter = new Converter.ForArrays.SetBased( set )
val input = Array( 'A', 'B', 'C' )   
val converted : Container.ForArrays[Char] = converter.convert( input )
Это было полезно?

Решение

No, you can't. For the same reason you can't narrow argument types or widen return types when overriding a method (but can narrow return type). Here is what you can do, however (for your fallback solution):

trait Converter {
  type Constraint[T]
} 

trait ForArrays extends Converter {
  def convert[E]( input: Array[E] )( implicit ev : Constraint[T] ) : Container.ForArrays[E]
}

case class SetBased[F](set: Set[F]) extends Converter {
   type Constraint[T] = T =:= F
   def convert[E]( input: Array[E] )( implicit ev : E =:= F ) = ...
} 

Другие советы

I'm going to assume that Container.ForArrays is a subclass of Container, without this, Converter.ForArrays.convert won't match the signature of the overridden Converter.convert

Try writing it something like this:

object Converter {
  trait ForArrays extends Converter {
    def convert[E] (input: Array[E]): Container.ForArrays[E]
  } 
}

Regarding your fallback solution. If two types are the same, then just use the same type param!

case class SetBased (set: Set[F]) extends Converter.ForArrays {
  def convert (input: Array[F]): Container.ForArrays[F] = {...}
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top