Search code examples
scalaencapsulationmixinstraits

encapsulation for mixin's members in Scala


Traits in Scala can be used as both mixins and interfaces. It leads to some inconsistence - if I want to close some method inside trait, I just can't do that:

object Library {
    protected trait A { def a: Int = 5 }
    trait B extends A { private override def a: Int = super.a }
    //I want to close `a` memeber for all traits extending B; it's still possible to open it in some another trait `C extends A`, or even `Z extends B with C`
}

// Exiting paste mode, now interpreting.

<console>:10: error: overriding method a in trait A of type => Int;
 method a has weaker access privileges; it should not be private
           trait B extends A { private override def a: Int = super.a }
                                                    ^

Such error is totally fine from LSP-perspective as I (or compiler) may want to cast it to the supertype A. But if i'm just using it as mix-in I never need to do that actually, for instance in some Cake-pattern variation. I'll do something like:

 import Library._
 object O extends B with K with L with App

and that's it. I can't even access trait A here. I know, there is type inference which may go up to super-type, but it's just a "line of types" so compiler could just skip A here and go on (of course it's very very theoretical). Another example - here I had to provide default implementation for method, which I don't really need.

The current solution I use is OOP-composition, but it's not so flexible (as linearization doesn't work here) and not much compatible with mix-ins concept. Some projects I've seen, they actually do mixins and have "over9000" redundand visible members. Several years ago there was an idea to "mark" such mixins composition by with keyword specified instead of extends, but can't even find that thread now.

So, is there any better practices for ad-hoc member encapsulation?


Solution

  • It's not general solution, but it's possible to close methods from the outside world inside one module:

    object Library {
      protected trait A { 
        private[Library] def a: Int = 5 
        private[Library] def b: Int = 7 
      }
      trait B extends A { 
        def b = super.b 
      }
    }
    
    import Library._
    object C extends B
    
    scala> C.a
    <console>:179: error: method a in trait B cannot be accessed in object C
                  C.a
                    ^
    scala> C.b
    res131: Int = 7
    

    So we're just inverting encapsulation here. If A should be also open for extension:

    object Library {
      protected trait _A { 
        private[Library] def a: Int = 5 
        private[Library] def b: Int = 7 
      }
      trait B extends A { /*...*/ }
      trait A extends _A {
        override def a = super.a 
        override def b = super.b
      }
    }
    

    So, maybe too boilerplate, but at least it works.

    P.S. The idea is partially inspired by another deleted answer which didn't work :)