Reputation: 1313
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.
Factory[+F <: Factory[F]]
and some concrete implementations irrelevant here. This is covariant. This class has a method that creates Products - see next point.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.)Iterable[Factory[_]]
. This is the first part of trouble. Scala seems to understand _
here as Any
, disregarding the constraint F <: Factory[F]
on the type itself.Map[Factory[_],Product[_]]
and also tried Map[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 as Any
.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:
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. Map
of F to PThe 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
Upvotes: 3
Views: 837
Reputation: 119847
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.
Upvotes: 1
Reputation: 119847
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.
Upvotes: 2
Reputation: 297155
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
}
Upvotes: 5
Reputation: 558
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]
Upvotes: 1