问题
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
, Set
s, 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