0

I am trying to build a framework with a hierarchy of Contexts (that hold immutable data) and Modules that create actors work with the data. Subclasses on Context include more data (e.g., a RemoteContext would have information about how to communicate with remote hosts). There are also Factory objects to make the relevant objects, which is part of the data in the Context.

I can define the hierarchy with sub-classes, and that works fine. Once everything is defined, a Mediator object initializes each Module by passing the context.

The code below shows that basic structure.

import java.{ util => ju}

trait Factory
trait Context[F <: Factory]

trait SomeContext[F <: Factory] extends Context[F]

trait MediatorModule[C <: Context[_ <: Factory]] {
    def loadModule(c: C)
}

trait Mediator[C <: Context[Factory]] {
    val context: C
    def getModules: ju.List[MediatorModule[_ >: C]]
    def run() = getModules.forEach(_.loadModule(context))
}

trait OtherFact extends Factory
trait OtherContext extends SomeContext[OtherFact]

class SomeModule extends MediatorModule[SomeContext[Factory]] {
    def loadModule(c: SomeContext[Factory]): Unit = { }
}
class OtherModule extends MediatorModule[OtherContext] {
    def loadModule(c: OtherContext): Unit = { }
}

class OtherContextImpl extends OtherContext {

}

class OtherMediator extends Mediator[OtherContext] {
    val context: OtherContext = new OtherContextImpl
    def getModules: ju.List[MediatorModule[_ >: OtherContext]] =
        ju.Arrays.asList(new SomeModule,
                         new OtherModule)
}

(The code was originally written in Java, which is why it is using Java lists).

As written, this fails to compile:

Test.scala:78:26: type mismatch;
[error]  found   : SomeModule
[error]  required: MediatorModule[_ >: OtherContext]
[error] Note: SomeContext[Factory] <: Any (and SomeModule <: MediatorModule[SomeContext[Factory]]), but trait MediatorModule is invariant in type C.
[error] You may wish to define C as +C instead. (SLS 4.5)
[error]         ju.Arrays.asList(new SomeModule,
[error]                          ^
[error] one error found

Following the compiler's suggestion by declaring trait MediatorModule[+C <: Context[_ <: Factory]] instead gives two errors:

Test.scala:52:20: covariant type C occurs in contravariant position in type C of value c
[error]     def loadModule(c: C)
[error]                    ^
[error] Test.scala:75:29: type arguments [OtherContext] do not conform to trait Mediator's type parameter bounds [C <: Context[Factory]]
[error] class OtherMediator extends Mediator[OtherContext] {
[error]                             ^

I can fix one with trait Context[+F <: Factory] but the co/contra variant error remains.

What can I do to fix that error? Also, how does +C translate back to Java?

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Troy Daniels
  • 3,270
  • 2
  • 25
  • 57
  • Without reading too much, usually you solve those with `def loadModule[C1 >: C](c: C1)` That way you flip the variance of **C**. I would suggest you to read first about variance, what does it mean and why it is important. Finally `+C` does not translate in anyway to **Java**, the JVM due type-erasure and other reasons does not manage variance. All that information is only used by the **Scala** compiler to avoid some bugs that are pretty easy to create on **Java**. – Luis Miguel Mejía Suárez Sep 25 '19 at 15:28
  • @Luis Miguel Mejía Suárez While it's true that `+C` doesn't translate to Java, what you said afterwards is not true. Scala and Java both rely on the same JVM and have the same type-erasure mechanism. But the thing is, Java language only supports **call-site** or **use-site** variance, for example `Foo extends Integer> covariantFoo = new Foo();`. Scala supports that (as `[_ <: Integer]`), but also supports **declaration-site** variance, e.g. `Foo[+C]`, saying that `Foo` is *always covariant in C*. – slouc Sep 25 '19 at 15:39
  • Your suggestion fixed the problem in the sample code. Unfortunately, in the production code, the relevant parts are in Java, so I probably can't just make the corresponding changes. – Troy Daniels Sep 25 '19 at 15:40
  • @slouc Well yes, but again that only works for the compiler. The JVM in general does not support the concept of variance nor type parameters _(and in general terms that is good)_. – Luis Miguel Mejía Suárez Sep 25 '19 at 15:43
  • @TroyDaniels Mixing of variance in **Scala** + **Java** is difficult, because both manage them differently _(as slouc said)_. What is your objective? Integrate a Java component to a Scala project? or a Scala component on a Java project? Or slowly migrate from Java to Scala? – Luis Miguel Mejía Suárez Sep 25 '19 at 15:45
  • @Luis Miguel Mejía Suárez Yeah, I agree. I just wanted to point out that Java does allow variance, but only use-site, while Scala allows both use-site and declaration-site. This is a language difference. When we move from the compiler to the JVM, however, all that type information is heavily reduced in the same way for both languages. – slouc Sep 25 '19 at 15:50
  • 1
    @slouc Yeah, I am not sure if you misread or I write it bad _(since englih is not my native language)_. But, I (tried to) say that the one that did not had variance was the **JVM** not **Java** as a language _(but the latter does have a less powerful than **Scala**, which allows some bug, specially with `Arrays`)_. – Luis Miguel Mejía Suárez Sep 25 '19 at 16:00
  • @LuisMiguelMejíaSuárez The framework is written in Java, and will probably stay there, since other projects are also using it and are in Java. I am currently trying to use it in Scala. In practice, some of the Modules and some Mediators are in Scala, and everything else is in Java – Troy Daniels Sep 25 '19 at 16:52
  • I am guessing that ` extends Foo>` and `[_ <: Foo]` probably get translated to (almost) the same (simple?) byte code, but `[+Foo]` gets rather more to handle the variance. Is that right? – Troy Daniels Sep 25 '19 at 16:55
  • @TroyDaniels again _variance_ does not exists at bytecode. _variance_ is a concept of the **type system** _(both Scala and Java have different ones)_ the JVM _(which is the **runtime system**, which both languages share)_ has `classes` not `types` you have _subclass_ relationships not _subtype_ relationship. My suggestion would be to write a wrapper over the framework on Scala, that wrapper probably would need to do some nasty things, `asInstanceOf` casts to make things compile. – Luis Miguel Mejía Suárez Sep 25 '19 at 17:02

1 Answers1

0

Try to make both Context and SomeContext covariant

trait Context[+F <: Factory]

trait SomeContext[+F <: Factory] extends Context[F]

Then the code compiles.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66