views:

297

answers:

2

Suppose I have two classes, Input and Output, which are designed to be connected to each other. Output produces values of some type, and Input consumes them.

class Input[T] {
  var output: Option[Output[_ <: T]] = None
}
class Output[T] {
  var input: Option[Input[_ >: T]] = None
}

It's okay if an Input and Output pair don't operate on the same kind of value as long as the Input type parameter is a supertype of the Output type parameter. Note that the type parameter in both classes is invariant; in the real versions it is used in both co- and contravariant positions.

I have a connect method elsewhere which sets up a link between an Input/Output pair:

def connect[T](output: Output[T], input: Input[_ >: T]) = {
  output.input = Some(input)
  input.output = Some(output)
}

If I call this method as below, I get a type error:

val out = new Output[String]
val in = new Input[AnyRef]
connect(out, in)

The error is:

test.scala:17: error: type mismatch;
 found   : Output[String]
 required: Output[AnyRef]
  connect(out, in)
          ^

I can resolve this by writing out the type parameter (in this case, I would write connect[String], but I think the compiler should be able to figure this out for me. How can I change the connect method so that the type parameter is inferred automatically?


Edit: For now, I've made connect a method of Output so it gets the type parameter automatically. This also has the added benefit that I can use the infix notation out connect in, but the design feels a little awkward.

I am still interested in why the compiler exhibits this behavior. I feel like it should be able to infer the type parameter. Is this actually working as specified?

+3  A: 

You will sometimes get better results if you use multiple parameter lists:

def connect[T](output: Output[T])(input: Input[_ >: T]) = {
  output.input = Some(input)
  input.output = Some(output)
}

connect(out)(in)

...and indeed in this case, it works.

extempore
Can you expand on why this is? *"sometimes get better results"* doesn't sound very deterministic!
oxbow_lakes
Sadly, the type inferencer isn't spec'ed, and sometimes isn't deterministic either.
Jorge Ortiz
A: 

I may be totally wrong, but I think the problem is when you tie together the input and the output: the input has an output restricted to a subtype of T, but the output has an input restricted to a supertype of T, the only type that can satisfy both conditions is T.

Input[T] -> Output[ _ <: T ]
Output[Q] -> Input[ _ >: Q ]

When you create the input with the output (replacing Q with _ <: T )you get:

Input[T]->Input[ _ >: [_ <: T] ]

The same as

Input[T]->Input[ _ <: T <: _ ]

Hence the type mismatch

GClaramunt
In my example, String and AnyRef satisfy my constraints. The Output produces Strings and requires an Input that consumes some supertype of String. The Input consumes AnyRefs and requires an Output that produces some subtype of AnyRef. Since String is a subtype of AnyRef, the constraints are satisfied.
Jay Conrod
The problem is that there is exactly one appropriate type parameter for connect, and that is the type parameter of the Output. When I specify this explicitly, it works fine. If I don't, it tries to use the type parameter of the Input, and I get a type error on the Output argument.
Jay Conrod
Sorry, I misread the Input[_ >: T] in the signature of connect[T](
GClaramunt