Please note that the Scala wikis are in a state of flux. We strongly encourage you to add content but avoid creating permanent links. URLs will frequently change. For our long-term plans see this post by the doc czar.
Skip to end of metadata
Go to start of metadata

Adding Virtual Classes to Scala

Brainstorming

Write down related thoughts here, without worrying about feasibility, peer-ridicule (wink),..

Are Packages Virtual Classes

It would be nice to be able to "subclass" java.io and override File to do security checks for example.

Ingo Adriaan, just to make sure I don't get it wrong, do you mean "can packages contain virtual classes"?

Adriaan Yep, that's what I meant, but it's too invasive for a first take on VC's anyway (smile)

Erik Virtual classes in "packages" (in 'attributes' slots in fragments) in gbeta are non-trivial. The semantics of this is similar to open classes or partial classes, i.e., it is a way to add declarations to a specified context in a physically separate location (e.g., in a different file). Ex in a slightly Scala-like syntax:

  // In file Foo.gb
  class Foo {
    <<SLOT FooLib:attributes>>; // declare this class open to additions
    def bar = ...
  }

  // In another file, fl1.gb
  ORIGIN 'Foo' // search path for SLOTs
  -- FooLib:attributes --
  class Baz <: ...  // add this declaration to the location FooLib

  // In yet another file, fl2.gb
  ORIGIN 'Foo'
  -- FooLib:attributes --
  class Baz <: ...

The reason why this construct is a bit hard to handle is that you may have any number of 'other' files that wish to add declarations to the location FooLib, and name clashes are hard to detect until link time because these other files should be separately compilable. Another thing is that this kind of feature is incompatible with a requirement that the exact superclass of a given class (here: a version of a virtual class) must be known somewhere in the program, because we may choose to link Foo.gb with zero or more of the files fl1.gb and fl2.gb (if zero, Foo has no nested Baz; if both, Foo has a name clash on Baz; if one, the choice between fl1.gb and fl2.gb determines the exact class).

So is this similar to what you mean by having virtual classes in packages? In that case it's very interesting, but not trivial. (wink)

Geoff I do not think it would be feasible to "subclass" packages using the virtual class mechanism, as it seems very important to the virtual class encoding that the initial enclosing "module" declare the classes as virtual.

Import statement

Maybe this is a good time to make import more powerful. Since packages can be considered virtual classes.
David Pollak posted his thoughts about a better import, I think we should consider them as part of the design of VC's.

Geoff It may be feasible to consider packages as virtual classes, but I think this would require that the classes in a package be defined as virtual from the start (see above). However, it is not really clear to me
how this interacts with what David is proposing. Though I am not entirely clear on what he is proposing. My interpretation is that currently

import foo._

only makes the contents of foo visible in the current scope, rather than acting more like open in Standard ML, for example, and actually putting the contents of a module into the current
scope. However, I am guessing that David is not proposing that import actually pull in code, but that import should be transitive: if you import package1 and package1 imports package2 then you have also imported package2 by importing package1.

Example expansion by Martin

  /*
    class A {
      class C(x: Int) <: { var y = x ; def f(z: Int) = z + 1 }
      class D(z) extends C(f(z)) { override def f(z:Int) = z + 2 }
    }
    class B extends A {
      class C(x: Int) <: { def g = 2 }
    }
  */


  abstract class A {
    type C <: CT

    trait CT { self: C => val x: Int; val y = x; def f(z:Int) = z + 1 }

    type D <: C with DT

    trait DT extends { self: D => def f(z:Int) = z + 2 }

    trait preDT extends { self: D => val z: Int; val x = f(z) }

    def newC(x: Int): C
    def newD(x: Int): D
  }

  class Afinal extends A {
    type C = CT
    type D = C with DT

    class CC(_x:Int) extends { val x = _x } with CT

    def newC(x:Int): C = new CC(x)

    class DC(_z:Int) extends { val z = _z } with preDT with CT with DT {
      override def f(z:Int) = super.f(z)
    }

    def newD(z:Int):D = new DC(z)
  }

  abstract class B extends A {
    type C <: CT with CT2

    trait CT2 { self : C => def g = 2 }

    // affects D, so D is updated
    // I think this can be dropped.
    // type D <: C with DT // what is right linearization 
  }

  class Bfinal extends B {
    type C = CT with CT2
    type D = C with DT

    class CC2(_x:Int) extends { val x = _x } with CT with CT2

    def newC(x:Int): C = new CC2(x)

    class DC2(_z:Int) extends { val z = _z } with preDT with CT with CT2
      with DT { override def f(z:Int) = super.f(z) }

    def newD(z:Int): D = new DC2(z)
  }

Sean I'm not so happy with <:, it seems like virtual should be a class modifier (to state that the class can be refined) and then just use extends as with classes. Its weird to me that <: means both extends and the class is virtual, while extends means extends and the class is no longer virtual. But this is just a syntactic gripe.

Inheriting from virtual classes

said: Based upon my discussion with Martin I was under the impression we would need to disallow examples like the following, where a virtual class is inherited from in a subclass of its enclosing class:

    class A {
      class C(x: Int) <: { val y = x ; def f(z: Int) = z + 1 }
      class D(z: Int) extends C(f(z)) { override def f(z:Int) = z + 2 }
    }

    class B extends A {
      class C(x: Int) <: { def g = 2 }
      class E(w: Int) extends C(w + 1) { override def f(z:Int) = z + 3 }
    }

That is, having E inherit from C in B would need to be disallowed. However, I just worked out
what I think would be the translation based Martin's example, and it seems to be accepted by the Scala compiler without problems. Are there language features I am not taking into account, or is it not a functionally equivalent translation?

 abstract class A {
    type C <: CT

    trait CT { self: C => 
               val x: Int; 
               val y = x; 
               def f(z:Int) = z + 1 }

    type D <: C with DT

    trait DT extends { self: D => 
                       def f(z:Int) = z + 2 }

    trait preDT extends { self: D => 
                          val z: Int; 
                          val x = f(z) }

    def newC(x: Int): C
    def newD(x: Int): D
  }

  class Afinal extends A {
    type C = CT
    type D = C with DT

    class CC(_x:Int) extends { val x = _x } with CT

    def newC(x:Int): C = new CC(x)

    // Is this the correct mixin ordering?
    class DC(_z:Int) extends { val z = _z } with preDT with CT with DT {
      override def f(z:Int) = super.f(z)
    }

    def newD(z:Int):D = new DC(z)
  }

  abstract class B extends A {
    type C <: CT with CT2

    trait CT2 { self : C => 
                def g = 2 }

    type E <: C with ET

    trait ET extends { self: E => 
                       def f(z:Int) = z + 3 }

    trait preET extends { self: E => 
                          val w: Int; 
                          val x = w + 1 }

    def newE(x: Int): E
  }

  class Bfinal extends B {
    type C = CT with CT2
    type D = C with DT
    type E = C with ET

    class CC2(_x:Int) extends { val x = _x } with CT with CT2

    def newC(x:Int): C = new CC2(x)

    class DC2(_z:Int) extends { val z = _z } with preDT with CT with CT2
      with DT { override def f(z:Int) = super.f(z) }

    def newD(z:Int): D = new DC2(z)

    class EC(_w:Int) extends { val w = _w } with preET with CT with CT2
      with ET { override def f(z:Int) = super.f(z) }

    def newE(w:Int): E = new EC(w)
  }

wrote in e-mail:
I don't think there is any need to disallow that inheritance relation (i.e.,
inheriting from inherited virtual in same object), and the translation
should also work as-is.

in response:
Okay, maybe I just misunderstood what Martin was telling me. It could be he was talking about the restriction on inheriting at different nesting depths.

Virtual case classes

Geoff I think this is the correct way to compile virtual case classes

/*
    class A {
      case class C(x: Int) <: { val y = x ; def f(z: Int) = z + 1 }
    }

    class B extends A {
      case class C(x: Int) <: { def g = 2 }
    }
*/


  abstract class A {
    type C <: CT

    trait CT { self: C => 
               val x: Int; 
               val y = x; 
               def f(z:Int) = z + 1 }

    object C { 
      def unapply(arg:C) : Option[Int] = Some(arg.x)
    }

    def newC(x: Int): C
  }

  class Afinal extends A {
    type C = CT

    class CC(_x:Int) extends { val x = _x } with CT

    def newC(x:Int): C = new CC(x)
  }

  abstract class B extends A {
    type C <: CT with CT2

    trait CT2 { self : C => 
                def g = 2 }

  }

  class Bfinal extends B {
    type C = CT with CT2

    class CC2(_x:Int) extends { val x = _x } with CT with CT2

    def newC(x:Int): C = new CC2(x)
  }


  object Tester {
    def main(args:Array[String]) : unit = {
      val m = new Bfinal
      val z = m.newC(5) 

      z match {
        case m.C(y) => Console.println("got " + y)
        case _ => Console.println("got something else")
      }
    }
  }

Separate modules

Ingo I am currently using the following idiom in ScalaFX, but I realized that it is actually not possible with virtual classes. It only works for me because I currently have to write all boiler plate code for assembling the modules myself anyways. Here is an example (refering to the A of the first example from above):

/*class Module {
  val a: A
  
  class R(z: Int) extends a.C(z) { override def f(x: Int) = 2*x }
}*/

abstract class Module {
  val a: A
  
  type R <: a.C with RT
  trait RT extends a.CT { self: R => override def f(x: Int) = 2*x }
  trait preRT extends { self: R => val z: Int; val x = z }
  def newR(x: Int): R
}
class Modulefinal extends Module {
  type R = a.C with RT
  
  class RC(_z: Int) extends { val z = _z } with preRT with a.CT with RT // ERROR: cannot know concrete C
  def newR(x: Int): R = new RC(x) // ???
}

object Test {
  val mod1 = new Module { val a: A = new A } // here it's just CT
  val mod2 = new Module { val a: A = new B } // here it's CT with CT2
}

We cannot have something like Modulefinal, since we don't know the precise C that R will extend at compile time. The compiler would need to create a concrete class R for every instantiation of Module at runtime, i.e., when it knows the value of a. I mainly used this idiom for namespace subdivision. I don't see another (static) way to do it. Is it safe to say that a class hierarchy involving a virtual base class must entirely go into a single namespace? This might be a problem for large hierarchies. Can we/Do we want to address this issue? Am I missing something?

Erik We discussed this. In gbeta it is supported to inherit from a class which is not statically known, but the planned approach for Scala would be to require that the superclass is statically known at each level. So when inheriting from a virtual class (e.g., R extends a.C) the exact set of mixins included in that virtual class (a.C) should be known in every version of the enclosing class (here we only have one: Module), such that a concrete class and a factory method can be generated. The rationale is that it should be possible to determine at compile-time the exact value (i.e., the exact set of mixins) of all classes that may exist at run-time, because it would be a too radical change to the Scala execution model to allow for run-time creation of classes. In the concrete example 'val a' could refer to instances of different classes with different values for the virtual class C, and hence this could not be supported without run-time creation of classes, i.e., this kind of program should be rejected.

Related Work

CaesarJ

said:
> One thing we should keep in mind is that CaesarJ (http://caesarj.org/) uses a scheme
> that may actually be very similar, in order to provide virtual classes in an
> extension of Java. So we'd need clarify the differences.

Open classes

Sean My encoding, which I guess is related to the current encoding, is based on the open class pattern presented in the Jiazzi paper, except with language-level mixins we can get around the problem that we can't extend the open type parameters directly.

  • No labels