开发者

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.

  1. 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.
  2. 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.)
  3. 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 as Any, disregarding the constraint F <: Factory[F] on the type itself.
  4. 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 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:

  1. 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.
  2. Iterates over that generic collection and executes a function passed to it. This function is what actually calls the factory method.
  3. 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 nth 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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜