简体   繁体   中英

Specifying the requirements for a generic type

I want to call a constructor of a generic type T , but I also want it to have a specific constructor with only one Int argument:

 class Class1[T] {
   def method1(i: Int) = {
     val instance = new T(i)  //ops!
     i
   }
 }

How do I specify this requirement?

UPDATE : How acceptable (flexible, etc) is it to use something like this? That's a template method pattern.

 abstract class Class1[T] {

   def creator: Int => T

   def method1(i: Int) = {
     val instance = creator(i)  //seems ok
     i
   }
 }

Scala doesn't allow you to specify the constructor's signature in a type constraint (as eg C#).

However Scala does allow you to achieve something equivalent by using the type class pattern. This is more flexible, but requires writing a bit more boilerplate code.

First, define a trait which will be an interface for creating a T given an Int .

trait Factory[T] {
  def fromInt(i: Int): T
}

Then, define an implicit instance for any type you want. Let's say you have some class Foo with an appropriate constructor.

implicit val FooFactory = new Factory[Foo] {
  def fromInt(i: Int) = new Foo(i)
}

Now, you can specify a context bound for the type parameter T in the signature of Class1 :

class Class1[T : Factory] {
  def method1(i: Int) = {
    val instance = implicitly[Factory[T]].fromInt(i)
    // ...
  }
}

The constraint T : Factory says that there must be an implicit Factory[T] in scope. When you need to use the instance, you grab it from implicit scope using the implicitly method.

Alternatively, you could specify the factory as an implicit parameter to the method that requires it.

class Class1[T] {
  def method1(i: Int)(implicit factory: Factory[T]) = {
    val instance = factory.fromInt(i)
    // ...
  }
}

This is more flexible than putting the constraint in the class signature, because it means you could have other methods on Class1 that don't require a Factory[T] . In that case, the compiler will not enforce that there is a Factory[T] unless you call one of the methods that requires it.


In response to your update (with the abstract creator method), this is a perfectly reasonable way to do it, as long as you don't mind creating a subtype of Class1 for every T . Also note that T will need to be a concrete type at any point that you want to create an instance of Class1 , because you will need to provide a concrete implementation for the abstract method.

Consider trying to create an instance of Class1 inside another generic method. When using the type class pattern, you can extend the necessary type constraint to the type signature of that method, in order to make this compile:

def instantiateClass1[T : Factory] = new Class1[T]

If you don't need to do this, then you might not need the full power of the type class pattern.

When you create a generic class or trait, the class does not gain special access to the methods of whatever actual class you might parameterise it with. When you say

class Class1[T]

You are saying

  1. This is a class which will work with unspecified type T.
  2. Most of its methods will take instances of type T as a parameter or return T.
  3. Any variance annotations or type bounds attached to the type parameter will be applied whenever it appears as a parameter of one of Class1's methods.
  4. There is no such thing as type "Class1" but there may be an arbitrary number of derived classes of type "Class1[something]"

That's all. You get no special access to T from within Class1, because Scala does not know what T is. If you wanted Class1 to have access to T's fields and methods, you should have extended it or mixed it in.

If you want access to the methods of T (without using reflection), you can only do that from within one of Class1's methods which accepts a parameter of type T. And then you will get whichever version of the method belongs to the specific type of the actual object which is passed.

(You can work around this with reflection, but that is a runtime solution and absolutely not typesafe).

Look at what you are trying to do in your original code snippet...

  1. You have specified that Class1 can be parameterised with any arbitrary type.
  2. You want to invoke T with a constructor which takes a single Int parameter

But what have you done to promise the Scala compiler that T will have such a constructor? Nothing at all. So how can the compiler trust this? Well, it can't.

Even if you added an upper type bound, requiring that T be a subclass of some class which does have such a constructor, that doesn't help; T might be a subclass which has a more complex constructor, which calls back to the simpler constructor. So at the point where Class1 is defined , the compiler can have no confidence about the safety of constructing T with that simple method. So that call cannot be type-safe.

Class-based OO isn't about conjuring unknown types out of the ether; it doesn't let you plunge your hand into a top-hat-shaped class loader and pull out a surprise. It allows you to handle arbitrary already-created instances of some general type without knowing their specific type. At the point where those objects are created , there's no ambiguity at all.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM