Stu B.
Stu B.

Reputation: 138

How can two coupled Scala generic type constructors refer to each other as type parameters?

In Java 1.6.0_21, the first example below compiles fine, and I think that's because the parameter type bounds are bare. That is, in the "Z extends Zen" bound below, Java allows Zen to slide by as the name of a raw, non-generic type (equivalent to the runtime "erased" type). This might be wrong and bad, but it also can be useful, or at least wacky good times on the bus ride home:

public class CorefTest {

    public static interface Tao<Z extends Zen> {
    }

    public static interface Zen<T extends Tao> {
    }
}

In Scala 2.8.0.final, the below compiles great up through CleanOceanWithFish, showing some basic type param hookups. But when we get to Zen and Tao being interdependent generic types, the Scala compiler rejects my weaving construct. See compiler errors in comments.

package heaven.piece
class Lucky {
    trait Water {}
    trait CleanWater extends Water {}
    trait Sea [W <: Water] {}
    trait Fish[S <: Sea[CleanWater]] {}

    trait CleanOceanWithFish[F <: Fish[CleanOceanWithFish[F]]] 
                   extends Sea[CleanWater]{}

// Above code compiles fine, but the type constructor pair below doesn't compile 

    trait Tao[Z <: Zen[Tao[Z]]]{};

    trait Zen[T <: Tao[Zen[T]]]{};
}
// error: type arguments [Lucky.this.Tao[Z]] do not conform to trait Zen's 
//     type parameter bounds [T <: Lucky.this.Tao[Lucky.this.Zen[T]]]

// error: type arguments [Lucky.this.Zen[T]] do not conform to trait Tao's 
//     type parameter bounds [Z <: Lucky.this.Zen[Lucky.this.Tao[Z]]]

So, how can I properly tie the Scala (2.8.0) knot between Tao and Zen?

It's certainly a contrived example, but what I really want is to use Scala to extend some real Java types I have working in the above form (which existential types via "forSome" and "[_]" are not helping me with, so far). I think that getting Zen and Tao compiling in Scala will probably show the way to that Java extension. If you can consider the Java extension issue in your answer, so much the better. Thanks for any help!

Update posted after the very helpful first two answers below from Nikita S. and Kris N.

I empirically learned some more about various Java+Scala coreference scenarios. The upshot is that when we want interoperable coreferent types in both Java and Scala, then this Java construct:

public static interface JavaFunTao<JFZ extends JavaFunZen<? extends JavaFunTao<JFZ>>> {
    public JFZ consider(JFZ someZen, JavaFunTao<JFZ> otherTao);
}
public static interface JavaFunZen<JFT extends JavaFunTao<? extends JavaFunZen<JFT>>> { 
    public JFT meditate(JFT someTao, JavaFunZen<JFT> otherZen);
}

provides more specific typing than my first Java example at top (avoiding the raw types), and is then properly extensible in Scala as follows:

class HiFunTao[HFZ <: HiFunZen[  _ <: HiFunTao [HFZ]]] extends JavaFunTao[ HFZ] {
    override def consider(someZen: HFZ, otherTao: JavaFunTao[HFZ]) : HFZ = {
        println (this.toString() + " is considering " + someZen + " and " + otherTao);
        someZen
    }
}
class HiFunZen[HFT <: HiFunTao[ _ <:  HiFunZen [HFT]]] extends JavaFunZen[ HFT] {
    override def meditate(someTao: HFT, otherZen: JavaFunZen[HFT]) : HFT = {
        println (this.toString() + " is meditating on " + someTao + " and " + otherZen);
        someTao
    }
}

I verified that we can make simple concrete types based on these, instantiate them, and invoke their methods. The key step in both the Java and the Scala is placing the bounded wildcard at the point where the type parameter tree loops back into the current declaration type, i.e. the "? extends" in java, and the "_ <:" in Scala.

Upvotes: 4

Views: 758

Answers (3)

Jugbot
Jugbot

Reputation: 162

Kris' answer didn't quite work for my needs. I wanted the interior to have mutable data which is not possible with covariant types.

You can get around the need for covariance if you specify a "self" type

trait Tao[Z <: Zen[Z,T], T <: Tao[Z,T]] {self: T =>
  var t: Z = _
  def link(t: Z) = {
    this.t = t
    t.t = this
  }
}

trait Zen[Z <: Zen[Z,T], T <: Tao[Z,T]] {self: Z =>
  var t: T = _
}

class Test1 extends Tao[Test2, Test1] {
  def test=t // Test2
}

class Test2 extends Zen[Test2, Test1] {
  def test=t // Test1
}

//Illegal inheritance, self-type Test3 does not conform to T
//This can only succeed if Test3 also extends Test1
class Test3 extends Tao[Test2, Test1] {
  def test=t
}

It is a little more verbose and requires you to pass in the class that is extending the trait as a type parameter, but currently there is no way to surface that type automatically (using this.type is not possible in that position)

Besides the verbosity, this solution provides the best usability and type safety, I think.

Upvotes: 0

Kris Nuttycombe
Kris Nuttycombe

Reputation: 4580

Since neither Zen nor Tao are covariant, there's a substitutability problem in your zen and tao. This compiles fine for me under 2.8.1:

trait Tao[+Z <: Zen[Tao[Z]]]

trait Zen[+T <: Tao[Zen[T]]]

Of course, if you want to meditate on Z or T then this is also not going to work for you, at least not exactly as stated because of the issue of having a covariant type parameter in a contravariant position. You can get around that problem like this:

trait Tao[+Z <: Zen[Tao[Z]]] {
  def meditate[M >: Z](m: M) = ()
}

trait Zen[+T <: Tao[Zen[T]]] {
  def meditate[M >: T](m: M) = ()
}

but this may still produce constraints on your implementation that you may not want. It's confusing, at the least. :)

Update:

This also, by the way, avoids the problem mentioned in the update of NSkvortsov's answer. This compiles fine:

class Zazen extends Zen[Tao[Zazen]]

Upvotes: 3

Nikita Skvortsov
Nikita Skvortsov

Reputation: 4923

I think, this is what you need:

class MyClass {

  trait Tao[Z <: Zen[_]]{};
  trait Zen[T <: Tao[_]]{};

}

This snippet successfully compiled by Scala 2.8.1

Update

Unfortunately, extending CorefTest.Zen and CorefTest.Tao in scala is impossible. Here is why.

The only way to implement interfaces in java is to use raw types:

public class TaoImpl<Z extends CorefTest.Zen> implements CorefTest.Tao<Z> { }

public class ZenImpl<T extends CorefTest.Tao> implements CorefTest.Zen<T> { }

than one can instantiate classes like this:

TaoImpl<ZenImpl> tao = new TaoImpl<ZenImpl>();
ZenImpl<TaoImpl> zen = new ZenImpl<TaoImpl>();

But scala does not support raw types. So TaoImpl and ZenImpl just can not be defined. Please see this e-mail thread and issues #2091 and #1737 for detailed discussion

Upvotes: 2

Related Questions