Justin du Coeur (jducoeur) wrote,
Justin du Coeur
jducoeur

Mix-ins and self-types

[A short burble for the programmers.]

One of the odder concepts in Scala, which I'm beginning to truly appreciate, is the "self-type". Here's an example...

Querki is full of what I originally was calling "methods". ("Functions" is more technically correct at the moment, and I'm moving in that direction, but roll with "methods" for now.) There are dozens of these, scattered all over the code. They follow a few distinct forms, so I gradually invented a bunch of convenience base classes named "SingleContextMethod", "ThingAndPropMethod", and so on, which the various Methods inherit from.

So far, this is all well and good. But in the new Ecology-based world, it turns out that it would be useful for these definitions to know about their context -- basically, for the methods to know that they are defined inside an Ecot, so that they can access the Ecology through it. Moreover, it really should *not* be legal to define a method anywhere outside an Ecot, since the Ecots are what build the world.

After a little thought, it turns out to be trivial to do this, and it illustrates an aspect of how Scala's version of multiple inheritance works. I took all of these base classes, and bundled them up into a trait (which is kind of like the midpoint between a class and a Java interface), defined roughly like this:
trait MethodDefs { self:Ecot =>
  
  val Conventions = initRequires[querki.conventions.Conventions]
 
  class SingleThingMethod(tid:OID, name:String, summary:String, details:String, action:(Thing, QLContext) => QValue) extends InternalMethod(
    toProps(
      setName(name),
      Conventions.Summary(summary),
      Conventions.Details(details)
    ))
  ... etc
}
What's going on here? Basically, that "self" declaration at the top says, "I am a trait that can *only* be mixed into an object that also implements Ecot". Of course, having declared that, it means that I can now use all of the methods of Ecot without any additional declaration -- this trait knows that it *is* an Ecot, and can behave as such. So it can directly use initRequires(), which is defined in Ecot.

That being done, a typical use case is as simple as:
class DataModelAccessEcot(e:Ecology) extends Ecot(e) with DataModelAccess with MethodDefs { ...
I just mix in MethodDefs, and *poof* -- I get the functionality. And MethodDefs can do anything that the contents of an Ecot can do. Moreover, mixing in MethodDefs implicitly declares that this Ecot is dependent on Conventions for its initialization. (Since creating a method requires the existence of the "Summary" and "Details" properties, which are defined in Conventions.)

There are many reasons why I am so fond of Scala. One of them is the fact that they finally got multiple inheritance just right -- strongly-typed, but massively powerful and easy to use. I'm only now starting to understand how important self-types are to that, and how they allow you to decompose code much more precisely and correctly than you can in Java or C#...
Tags: programming, scala
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your reply will be screened

    Your IP address will be recorded 

  • 4 comments