11

I have some troubles having Scala to infer the right type from a type projection.

Consider the following:

trait Foo {
  type X
}

trait Bar extends Foo {
  type X = String
}

def baz[F <: Foo](x: F#X): Unit = ???

Then the following compiles fine:

val x: Foo#X = ???    
baz(x)

But the following won't compile:

val x: Bar#X = ???    
baz(x)

Scala sees the "underlying type String" for x, but has lost the information that x is a Bar#X. It works fine if I annotate the type:

baz[Bar](x)

Is there a way to make Scala infer the right type parameter for baz?
If not, what is the general answer that makes it impossible?

betehess
  • 817
  • 4
  • 18
  • 2
    Not an answer, but it's worth noting that if you type `x` using a type designator instead of a type projection, it works—including e.g. `object BAR extends Bar; val x: BAR.X = "a"; baz(x)`. – Travis Brown Feb 06 '13 at 04:26
  • 3
    Also worth noting: you can convince the compiler that you really _do_ want `x` to be typed as something more or less like `Bar#X` with the incredibly ugly `val x: b.X forSome { val b: Bar } = "a": b.X forSome { val b: Bar }`. – Travis Brown Feb 06 '13 at 04:44
  • Seems like weird use case. Why do you want to do this? – Jesper Nordenberg Feb 06 '13 at 11:36
  • @JesperNordenberg the use case comes from [banana-rdf](https://github.com/w3c/banana-rdf/blob/master/rdf/src/main/scala/RDF.scala#L3). If I'm using a subtype of the `RDF` trait directly, then I can't benefit from many of the implicit functions like [this one](https://github.com/w3c/banana-rdf/blob/master/rdf/src/main/scala/RDF.scala#L3t). – betehess Feb 06 '13 at 13:56

2 Answers2

2

The program compiles by adding this implicit conversion in the context:

implicit def f(x: Bar#X): Foo#X = x

As this implicit conversion is correct for any F <: Foo, I wonder why the compiler does not do that by itself.

betehess
  • 817
  • 4
  • 18
1

You can also:

trait Foo {
    type X
}
trait Bar extends Foo {
    type X = String
}
class BarImpl extends Bar{
  def getX:X="hi"
}
def baz[F <: Foo, T <: F#X](clz:F, x: T): Unit = { println("baz worked!")}
val bi = new BarImpl
val x: Bar#X = bi.getX
baz(bi,x)

but:

def baz2[F <: Foo, T <: F#X](x: T): Unit = { println("baz2 failed!")}
baz2(x)

fails with:

test.scala:22: error: inferred type arguments [Nothing,java.lang.String] do not conform to method baz2's type parameter bounds [F <: this.Foo,T <: F#X]
baz2(x)
^
one error found

I think basically, F <: Foo tells the compiler that F has to be a subtype of Foo, but when it gets an X it doesn't know what class your particular X comes from. Your X is just a string, and doesn't maintain information pointing back to Bar.

Note that:

def baz3[F<: Foo](x : F#X) = {println("baz3 worked!")}
baz3[Bar]("hi")

Also works. The fact that you defined a val x:Bar#X=??? just means that ??? is restricted to whatever Bar#X might happen to be at compile time... the compiler knows Bar#X is String, so the type of x is just a String no different from any other String.

nairbv
  • 3,618
  • 1
  • 21
  • 25
  • Yes, the compiler only sees the `String` and looses the `Foo` context despite annotating the type with `Bar#X` instead of `String`. My only solution so far is to always abstract over `Foo` and provide `Bar` at the very end. But this is really a problem for me as it restricts what I can do `baz`... (in my case, `baz` is also an implicit) – betehess Feb 11 '13 at 20:52
  • Right. you could think of it this way: type X is a member of Foo, kinda like with other static vs member variables in Java. Trying to access it without an instance is like trying to access a member variable without an instance. – nairbv Feb 12 '13 at 03:15
  • In: `trait HasType{type X} class Foo(val t:HasType){type A=t.X}`. You could have many `Foo`'s with different `A`'s, each dependent on the particular passed `t` (instance of `HasType`). The type is dynamic and so has to be specified somehow. Foo#A in the example here doesn't tell you or the compiler any more about `A` than Person.age would tell you about a person instance's age. An instance `t` provides the type info. – nairbv Feb 12 '13 at 03:16
  • Maybe you could do something statically (in scala that means with the companion object)? `object Foo { type X = String } class Foo; val s:Foo.X="hi"` ? – nairbv Feb 12 '13 at 03:19