问题
After working through some examples of Scala Higher-kinded types in this tutorial, I started wondering if it is possible to write a method that generically handles two subclasses of a trait that is defined as a higher-kinded type.
The tutorial defines (a slightly more complex version of) this trait:
trait ContainerFactory[M[_]] { def put[A](x: A): M[A] }
Which I understand as the signature of a type parameterized factory that creates different kinds of containers (Lists, Sets, etc., where the type of container is given by M) and where the type of the object inserted into the container via the put method is given by A. At the invocation site (I think that's the correct term) where you
instantiate the container, you specify the type of container you want (as in the line with the comment: //factory for List containers)
val factory = new ContainerFactory[List] { def put[A](x: A) = List(x) } // factory for List containers
factory.put("dog") // insert an element of type String to factory
res5: List[String] = List(dog) // and you get a List of String
factory.put(1)// insert an element of type Int to factory
res6: List[Int] = List(1) // and you get a List of Int
val factory2 = new ContainerFactory[Set] { def put[A](x: A) = Set(x)} // factory for Set containers
factory2.put("dog")
factory2.put(1)
My goal is to create a method that takes a ContainerFactory
and an object to put into the generated container. I would like that method to generate the appropriate container (List or Set) parameterized to hold the type of object that I pass in as the second object.
I think a method like the one below would be really cool and useful, but I am having trouble with the Scala syntax to get it to work. In fact, I don't even know if it is possible.
// Code below does not compile
// Method for generating container (of type defined by first arg) that contains the second argument, and
// which (after instantiation) is parameterized to hold only objects of that type:
def genContainer[M[T]](factory: ContainerFactory[M], item : T) = {
factory.put(item)
}
genContainer(factory2, "x")
// desired return value => Set[String] = Set(x)
genContainer(factory, 11)
// desired return value => List[Int] = List(11)
Note: the error I get when I try to define genContainer is:
<console>:10: error: not found: type T
def genContainer[M[T]]( factory : Container[M] , item : T) = {
Note 2: I can define a method like this, which takes a generic ContainerFactory
def genContainer[M[T]](factory: ContainerFactory[M]) = { }
But when I try to specify the second argument as type T (which is referenced in the parameterization) I get the error about T not found.
回答1:
You were really close:
def genContainer[T, M[_]](factory: ContainerFactory[M], item: T) = {
factory.put(item)
}
All you have to do is specify each type parameter as a top-level type parameter! And the compiler is smart enough to deduce these type parameters under many circumstances:
val factory = new ContainerFactory[List] { def put[A](x: A) = List(x) }
genContainer(factory, "foo") //No need to specify the type parameters!
回答2:
You're very close. The issue is that you need to declare your type parameters separately:
def genContainer[T, M[_]](factory: ContainerFactory[M], item: T) =
factory.put(item)
This is a little confusing because the following compiles:
def genContainer[M[T]](factory: ContainerFactory[M]) = "whatever"
The scope of the T here is limited to the inside of the M[...], though (see section 4.4 of the language specification for details). This can be convenient when you're declaring fancy bounds like M[T <: Foo[T]], but in general giving the type parameter of the type constructor a name is just noise, and it's best to go with M[_] (which is exactly equivalent to M[A]).
来源:https://stackoverflow.com/questions/29835894/how-to-generically-handle-scala-higher-kinded-types-when-coding-factories-for-ge