?

Log in

No account? Create an account
Previous Entry Share Next Entry
Mix-ins and self-types
device
jducoeur
[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#...

  • 1
Thank you! A pretty good explanation of what self-types are for, in Scala world.

not sure how is it better than extension methods in C#

Actually, it fits a rather different ecological niche than extension methods. Extension methods are primarily used after-the-fact -- they basically say "I am adding extra Stuff to class Foo". Scala does have that as well, in a somewhat different way: they're called "implicit classes", and the semantics are a tad different, but functionally they're fairly similar. (It also has implicit functions, which are mainly used to do a single functional transformation, but implicit classes are the standard mechanism to, as it's often described, "pimp my library".)

Mix-ins are more about functional decomposition of your inheritance tree -- pulling out functionality that some, but not all, subclasses of Foo are going to need. People routinely do *some* of these in any conventional OO language through the class hierarchy; the advantage of mix-ins is that it makes a lot more use cases easy, especially when the tree is beginning to get complex.

(Note that dynamic languages like Ruby have had this kind of capability for many years now. Doing it well in a strongly-typed environment is tricky, though.)

You can work around this in C# using extension methods, but there are definite seams -- it's not that the subclass actually *has* this feature, it's that you can make it *look* like it has this feature from the outside, with some extra imports. It works adequately most of the time, but it doesn't quite play in the same league.

(Note: I've been a C# fan for about as long as the language has been around -- the 10 years prior to my current project were largely spent programming in it. It's a good language, certainly head and shoulders better than Java. But Scala is of a newer generation, and that shows: you have to basically use both C# and F# to even get most of the features that are built into Scala...)

But worse than goto in Algol.

  • 1