7

I'd like to implement a java method that uses generics in scala (2.9.2). But I'm failing...

Java interface method:

public <T extends Number> void setAttribute(Key<T> key, Number value);

Scala code that want to implement that method:

def setAttribute[T <: Number](key: Key[T], value: Number) = {
  setAttributeLocal(key, value)  }

private def setAttributeLocal[T](key: Key[T], value: T) = {
  val stringValue = ConvertUtils.convert(value, classOf[String]).asInstanceOf[String]
  session = session + (key.getValue() -> stringValue)
}

Key looks like:

public class Key<T>

But this doesn't compile.

[error]  found   : mypackage.Key[T]
[error]  required: mypackage.Key[java.lang.Number]
[error] Note: T <: java.lang.Number, but Java-defined class Key is invariant in type T.
[error] You may wish to investigate a wildcard type such as `_ <: java.lang.Number`. (SLS 3.2.10)
[error]     setAttributeLocal(key, value)

I can't figure out what's the problem. Any suggestions/idea?

greez GarfieldKlon

GarfieldKlon
  • 11,170
  • 7
  • 31
  • 33

3 Answers3

4

It appears the compiler is unhappy with your call to setAttributeLocal. setAttributeLocal requires a Key[Number], but you are providing a Key[_ <: T]. In Java-Land this means you're trying to pass a Key<? extends Number> off as a Key<Number>.

The suggestion is to have setAttributeLocal accept Key<? extends Number> or Key[_ <: Number], depending on whether it is Java- or Scala-defined.

Ben Schulz
  • 6,101
  • 1
  • 19
  • 15
  • @GarfieldKlon I had to read this a few times... I think what Ben is suggesting is that if you're focused on fixing `setAttribute` then you may be looking at the wrong thing. The error you described is a type problem with `setLocalAttribute`, not `setAttribute`. What does your definition of `setLocalAttribute` look like? – Richard Sitze Jul 30 '12 at 14:39
  • @Richard - alright, that explains why I couldn't reproduce this – 0__ Jul 30 '12 at 14:46
  • We finally added this solution: `private def setAttributeLocal[T](key: Key[T], value: Object)` – GarfieldKlon Nov 02 '12 at 12:25
1

Something looks a bit off here.

Have you tried:

def setAttribute[T <: Number](key: Key[T], value: T) =
  setAttributeLocal(key, value)

It seems strange/bad to preserve the type T for the key, but not use it on the value. My guess is that's where you're getting an invariant error. You're trying to assing the value of type Number to a key of type T and the compiler isn't sure if it can't pass Number for T (while it knows it can pass T for Number).

Can we see more code?

jsuereth
  • 5,604
  • 40
  • 40
  • That's not an option, because it isn't an override. – GarfieldKlon Aug 02 '12 at 07:20
  • There are limitations in the type interfaces of Java and Scala. What you're doing in Java is type unsound, and so Scala disallows it. If you want to continue to use that type-wizardry, then you'll have to stick with Java's type system, sorry. – jsuereth Aug 02 '12 at 11:05
1

As @jsuereth already pointed out, there is a discrepancy between the signatures of setAttribute and setAttributeLocal, namely, that the former accepts a Key[T <: Number] but fixes the value that goes with the key to exactly be a Number, whereas the latter is more flexible and allows key and value to be T <: Number. This looks rather odd and you might want to reconsider that decision. It also leads to the problem explained by @Ben Schulz.

In any case, the compiler (2.9.1) is happy with the following setup:

MyI.java:

public interface MyI {
  public <T extends Number> void setAttribute(Key<T> key, Number value);
}

Key.java:

public interface Key<T> {
}

Test.scala:

object Test extends App {
  def setAttribute[T <: Number](key: Key[T], value: Number) = {
    setAttributeLocal(key, value)  }

  private def setAttributeLocal[T](key: Key[T], value: Number) = {
    /* Notice that value is now also of type Number. */
  }
}
Malte Schwerhoff
  • 12,684
  • 4
  • 41
  • 71