2

In the code below I get the correct type of the property (PropertyA) when I get it straight from the hashmap.

When I proxy this call through the get method in ClassAbstract the type is PropertyAbstract[_ <: A]

Is there a way to proxy the call to the hashmap and keep the correct type?

Another question is how can I add objects to the revs array with type checking?

class A
class B extends A
class C extends A

abstract class PropertyAbstract[T] {
  val revs = new java.util.ArrayList[T]
}

class PropertyA extends PropertyAbstract[B]
class PropertyB extends PropertyAbstract[C]

abstract class ClassAbstract {
  val props: scala.collection.immutable.HashMap[String, PropertyAbstract[_ <: A]]
  def get(prop: String) = props.get(prop).get
}

class Class extends ClassAbstract {
  val props = collection.immutable.HashMap(
      "prop1" -> new PropertyA,
      "prop2" -> new PropertyB
  )
}

object Test extends App {
  val the_class = new Class
  val proxied_prop = the_class.get("prop1")
  val direct_prop =  the_class.props.get("prop1").get

  // wont compile (found: B     required: _$1 <: A)
  proxied_prop.revs.add(new B) 
  // wont compile (found: B     required: C with B)
  direct_prop.revs.add(new B)
}

The wanted result is that I could add an element of type B to prop1, but not an element of type C

roelio
  • 399
  • 2
  • 13
  • 1
    For `the_correct_type_prop`, I get `PropertyAbstract[_ >: C with B <: A]`. This is expected if you look at the type of `the_class.props`. – Alexey Romanov Feb 02 '12 at 11:48
  • I also thought that was correct. But is there a way to add Objects of type `B` (and an error when trying to add `C`) to the `revs` array in `PropertyA`? I think the datastructures I use don't allow this. – roelio Feb 02 '12 at 11:59

2 Answers2

3

The problem is that get is defined in ClassAbstract and props is concretely defined in Class and has further type refinement that is not accessible to ClassAbstract. We need to provide a way for this additional type information to be passed from Class back to ClassAbstract. The following is one method of doing so.

abstract class ClassAbstract[P <: PropertyAbstract[_ <: A]] {
  val props: scala.collection.immutable.HashMap[String,P]
  def get(prop: String) = props.get(prop)
}

class Class extends ClassAbstract[Property] {
  val props = collection.immutable.HashMap(
      "prop" -> new Property
  )
}

This makes the direct get call return something of type Property.

Neil Essy
  • 3,607
  • 1
  • 19
  • 23
  • Thanks for your answer, but this is not what I want (see the updated the question). What if I have different type of properties in props and all the properties are subtypes of PropertyAbstract? – roelio Feb 02 '12 at 11:43
2

It seems that what you want is essentially a typed map. It's pretty obvious that what you are trying to do simply cannot work. When you call get, you receive a PropertyAbstract[X] for an unknown X (except in that it is a subtype of A). How can you then assume it takes Bs?

The solution is for your PropertyAbstract to be contravariant but this means that it cannot be a mutable collection in any sensible manner (it can be mutable of course, but what you can get out would be an A).

scala> class A; class B extends A; class C extends A
defined class A
defined class B
defined class C

scala> abstract class PropertyAbstract[-T] { val revs = new java.util.ArrayList[AnyRef] }
defined class PropertyAbstract

scala> class PropertyA extends PropertyAbstract[B]; class PropertyB extends PropertyAbstract[C]
defined class PropertyA
defined class PropertyB

scala> abstract class ClassAbstract {
   | val props: Map[String, PropertyAbstract[_ <: A]]
   | def get(prop: String) = (props get prop).get
   | }
defined class ClassAbstract

scala> class Class extends ClassAbstract { val props = Map("prop1" -> new PropertyA, "prop2" -> new PropertyB) }
defined class Class

scala>  val the_class = new Class
the_class: Class = Class@298508

scala> val proxied_prop = the_class.get("prop1")
proxied_prop: PropertyAbstract[_ <: A] = PropertyA@a269e2

scala> val direct_prop =  the_class.props.get("prop1").get
direct_prop: PropertyAbstract[C with B] = PropertyA@a269e2

The following compile:

scala> proxied_prop.revs.add(new B)
res0: Boolean = true

scala> direct_prop.revs.add(new B)
res1: Boolean = true

But of course you could put anything in there!

Perhaps you should take a look at Miles Sabin's shapeless for the sort of stuff you can do in terms of heterogeneously-typed collections.

oxbow_lakes
  • 133,303
  • 56
  • 317
  • 449
  • Thanks a lot for your effort. I'm still quite a newb when it comes to types, variance, generics etc. I now use this [solution](http://stackoverflow.com/a/4310959/730277) where the keys of the hashmap contain the type of the value. I also looked up the HList but I have to find out if this also works as a hashmap. – roelio Feb 02 '12 at 16:09