Trouble with generic collections in Scala
I'm puzzled as to how to solve this in Scala. In Java I'd give up on generics or use casts, but Scala is more strict than that. Here's what I have.
- An abstract base class
Factory[+F <: Factory[F]]
and some concrete implementations irrelevant here. This is covariant. This class has a method that creates Products - see next point. - An abstract base class
Product[+F <: Factory[F]]
that represents a product of the factory. These are also covariant and read-only. Any specific factory produces exactly one type of product (i.e. there aren't multiple different subtypes - FooFactory produces FooProduct, BarFactory produces BarProduct, etc.) - I must have a collection of factories that contains the concrete instances (Capt. Obvious speaking here). Presently this is
Iterable[Factory[_]]
. This is the first part of trouble. Scala seems to understand_
here asAny
, disregarding the constraintF <: Factory[F]
on the type itself. - I have an aggregated factory method tha开发者_运维问答t asks each factory to produce its product given same arguments. The result is a Map of factories to their products, presently coded as
Map[Factory[_],Product[_]]
and also triedMap[Factory[_],Product[Factory[_]]]
. This is another part where I get in trouble. The first_
is unlinked from the second_
and both seem to be implied asAny
.
What I can't do is create that map. It may or not be important, but I do it (and have to do it) by using yet another method made to look like a syntax construct. It comes from a trait and:
- Calls a declared method (call it
gather[B](fun: A => B): Map[A,B]
here, not implemented in the trait itself and generically typed) that gets the collection of factories. The actual factory type is generic and unknown. - Iterates over that generic collection and executes a function passed to it. This function is what actually calls the factory method.
- Returns an immutable
Map
of F to P
The compiler trouble in not in 'gather' method but in the code that calls it. I can't cast its result to Map[Factory[_],Product[_]]
because _ does not conform to F <: Factory[F]
in either place... I'd be more than happy to use a base type for _
, but that'd be Factory[Factory[Factory[Factory......(infinitely many times)]]]]]]....
and I don't know what to do.
Please help!
Learner
Most of the time, using _
in types is the wrong thing to do unless you know what you are doing. And if you don't understand existential types, then you most certainly don't know what you are doing. It is not that Scala thinks _
is Any
, it is that it knows _
can stand for anything -- be it Any
, Nothing
or anything in between.
To sum it up, Iterable[Factory[_]]
means Iterable[Factory[T]] forSome { type T }
. You can write it explicitly as Iterable[Factory[T]] forSome { type T <: Factory[T] }
to get the constrains you want, just as you can write it as Map[Factory[T], Product[T]] forSome { type T <: Factory[T] }
.
I'm not sure, though, if that really is what you need. Your questions is focusing on how you are trying to solve the problem, instead of focusing on how to solve the problem. It might be, for instance, that it would be better to use the following instead of generics:
abstract class Factory {
type T <: Factory
}
Pardon me if my Scala a bit rusty. I'm writing out of general OOP-and-generics sense.
It is not clear why Factory
or Product
need to be generics at all, much mess with covariance. Whether it is actually needed or not, it is necessary and sufficient to have a simple non-generic class FactoryBase
, of which all Factory
classes ultimately inherit. You stick that into an Iterable[FactoryBase]
.
It is also not clear why a Product
type contains any information about a Factory
. The opposite would have more sense, if you indeed need that information. A Factory
produces a Product
, hence, knows about it, so this knowledge might be reflected in the type. Then again, it might not.
Just in case you need genericity, covariance and full type information, you can use such hierarchies:
FactoryBase
Factory[+F <: Factory[F,P], +P <: Product[F,P]] <: FactoryBase
FooFactory <: Factory[FooFactory, FooProduct]
ProductBase
Product[+F <: Factory[F,P], +P <: Product[F,P]] <: ProductBase
FooProduct <: Product[FooFactory, FooProduct]
Feel free to omit any type parameters you don't need.
It is also not clear why you need a map that maps factories to products. Maps usually map things like names to entities. That is, given a name, find an existing entity with that name. Factories create new out of thin air. In any case, if you indeed need such a map, you can use Map[FactoryBase, ProductBase]
. This does not provide static guarantee that a FooFactory
maps to a FooProduct
and not to a BarProduct
, but a Map
cannot provide that. Then again, you can just stick a Product
inside its respective Factory
. You already know the mapping at compile time, there's no need for a run-time Map
. On the n
th hand, a product need not be associated with any factory after it is produced.
Perhaps I am misunderstanding you completely and you need to elaborate a lot on your design. As such its purpose and architecture is not clear at all.
I'm not quite sure that I understand your problem, but try to use something like :
gather[A : Factory, B : Factory](fun: A => B): Map[A,B]
Adding another answer instead of editing the old one.
I will talk not necessarily in terms of Scala type system, as the ideas here are applicable to many different languages.
First, before talking about maps, let me show how to make a deeper parallel hierarchies. It's really easy, just insert another level:
FactoryBase
Factory[+F <: Factory[F,P], +P <: Product[F,P]] <: FactoryBase
SomeFactoryGroup[+F <: SomeFactoryGroup[F,P], +P <: SomeProductGroup[F,P]] <: Factory[SomeFactoryGroup, SomeProductGroup]
FooFactory <: SomeFactoryGroup[FooFactory, FooProduct]
ProductBase
Product[+F <: Factory[F,P], +P <: Product[F,P]] <: ProductBase
SomeFactoryGroup[+F <: SomeFactoryGroup[F,P], +P <: SomeProductGroup[F,P]] <: Product[SomeFactoryGroup, SomeProductGroup]
FooProduct <: SomeProductGroup[FooFactory, FooProduct]
You can insert as many levels as you like. We need all of them, including the non-generic FactoryBase
and ProductBase
. Though they are not strictly necessary as it is possible to get away with existential types, these can get unwieldy. It is easier to say FactoryBase
than (SomeFactory | exist F <: Factory[F,P], exist P <: Product[F,P], SomeFactory <: F)
(this is not Scala syntax, deliberately). Sometimes existentials are very useful, but not in this example (perhaps you can use them in other parts of your system). Anyway, these FactoryBase
objects will be placed in an Iterable[FactoryBase]
to manage production runs.
Now to maps. There are production runs, during which each factory may produce one instance of a product (or perhaps none). We need, given a factory and a production run, find the product produced by that factory during that run.
One solution is what you have attempted: have production runs represented as (or contain, or whatever) maps from factories to products. Ignoring the types for a moment, in pseudocode:
productionrun.lookup(factory) = productionrun.mapFromFactoryToProduct.lookup(factory)
This may even work, if we stick to FactoryBase
and ProductBase
. But this approach is limiting. A map maps a bunch of things of the type A to a bunch of things of (perhaps different) type B. All keys are type-identical, and all values are type-identical. This is manifestly not what we have. All factories are of different types, and so are all products. We can work around the issue by forgetting part of type information, that is, by storing keys and values reduced to their least-common-denominator type. But what if we need to recover this type information later on? This is impossible to do statically, it is forgotten, lost forever.
The other solution is superficially symmetrical to the first one: have factories represent (or contain, in this case) maps from production runs to products.
factory.lookup(productionrun) = factory.mapFromProductionRunToProduct.lookup(productionrun)
Production runs in this case are represented just by their unique IDs. But at the type level this solution is very different, and much better, than the first one. All keys in each map are type-identical (they are all, in fact, type-identical, across different factories). All values in each map are type-identical (this type is specific to the factory). There's no type information lost at any stage.
So, to sum up. Have a class PR
that represents a production run. Create a method Factory[F,P].findProduct(pr:PR)->Product[F,P]
, implemented by means of Map[PR, Product[F,P]]
. Have a makeProduct
method accept a PR
value, and add the resulting product to the map, keyed by that PR
value. You can wrap this in a method PR.lookUpProductByFactory[F,P](f:Factory[F,P])->Product[F,P]
or some such if you want. Also, wrap it in a FactoryBase.findProduct(pr:PR)->ProductBase
, and wrap that into PR.lookUpProductBaseByFactoryBase(f:FactoryBase)->ProductBase
.
That's it. I hope of these two solutions will suit your needs.
精彩评论