Those are good points. The constructor thing doesn’t work as nicely for case classes etc., but it’s a decent workaround.
I’m not sure what I think about the override case. I hadn’t considered it very thoroughly. If it’s a common use case, I think you could allow in-block overrides over a wildcard export. The export ident for Type
form is still more verbose. I think it also gives more precise control.
Right now, traits are filled by type, even if the inheritance hierarchy isn’t followed. For instance,
class Fish { def fish = "salmon" }
trait FishBird{ def fish: String; def bird: String }
class PredatorPrey extends Fish with FishBird {
def bird = "osprey"
}
is fine because the name and type of fish: String
matches, even though FishBird
has no inheritance relationship with Fish
.
With the Type by instance
mode, either you lack the control to exclude things that you don’t mean to include, or you have to restrict instance
to being part of the inheritance hierarchy of Type
.
Then there’s the whole issue of what happens if you do innocuous forwarding that doesn’t seem to need a synthetic method:
class Foo {
// No point in creating synthetic forwarder
export Foo._
}
object Foo {
def whatever = "salmon"
}
trait Whatever {
def whatever: String
}
// Oops, this only works if we did create a synthetic forwarder
class Iffy extends Foo with Whatever {}
This suggests that if you allow exports to fulfill trait contracts, you must always create synthetic forwarders if there’s any possibility of extension; export isn’t just a compiler fiction. Or you have to transitively keep track of these exports and write synthetic methods the subclasses that rely upon the exports being available.
The problem with not allowing exports to fulfill traits in the Iffy
case is that you add an extra bit of information for every method–did you get this method from inheritance or from exporting? This is a huge headache. If the feature is going to be used heavily enough to be worth including, you’ll have to keep track of this. (This is one of the annoyances of extension methods, though admittedly you could just say that exports are syntactic sugar for extension methods.)
So, alternatively, we either have to restrict exports to things that can’t be extended, or everything in an extensible context has to be implemented as a forwarder.
If the latter, then I would argue that greater control is a virtue.
trait Base { def a: Int; b: Int; c: Int }
trait One extends Base { def a = 1 }
trait Two extends Base { def b = 2 }
trait C { def c = 3 }
class Count(one: One, two: Two) extends Base with C {
export one for Base.a
export two for Base.b
}
I don’t think the by
form has this kind of flexibility.
But note that given the synthetic forwarder thing,
class Count(one: One, two: Two) extends Base with C {
export one.a
export two.b
}
would have to be a compile-time error precisely because of extends Base
(otherwise it would be fine), and you could get around it with
abstract class AbstractCount(one: One, two: Two) extends C {
export one.a
export one.b
}
class Count(one: One, two: Two) extends AbstractCount(one, two) with Base {}
and this would have to work. You can’t disallow this, I don’t think, without making people memorize whether each method is an export or not (ugh!). But you don’t want to make them write it this way, I don’t think. You want it the single-class way, and you can’t handle that with by
notation. But you can handle it with export for
notation.
Anyway, I am rather leery of this whole thing. It seems like there are a lot of potential feature interactions that could get ugly.
If export
is syntactic sugar for creating extension methods, then I think everything is cool, or at least as cool as it’s going to be anyway. If it’s any more, then I think there are dragons, and I’m not totally convinced that they’re tamed yet.