Selftype: an idea from Scala for D2.0

eao197 eao197 at intervale.ru
Fri May 25 07:46:31 PDT 2007


	Hi!

There is an interesting article Scalable Component Abstractions  
(http://lamp.epfl.ch/~odersky/papers/ScalableComponent.pdf) which  
describes some features of Scala language (http://www.scala-lang.org) for  
building scalable software components. Two of these features, abstract  
members and selftypes, are illustrated by the following example of  
Subject/Observer pattern implementation:

abstract class SubjectObserver {
   type S <: Subject;
   type O <: Observer;
   abstract class Subject requires S { /* (1) */
     private var observers: List[O] = List();
     def subscribe(obs: O) = /* (2) */
        observers = obs :: observers;
     def publish =
        for (val obs <observers)
           obs.notify(this);  /* (3) */
   }

   abstract class Observer {
     def notify(sub: S): unit; /* (4) */
   }
}

Class SubjectObserver is used as special namespace for classes Subject and  
Observer. Type names S and O are declared as abstract members with  
restructions: S must be subclass of Subject, and O must be subclass of  
Observer. Concrete types for S and O must be defined by user of class  
SubjectObserver.

Class Subject is the base class for all future subject's implementations.  
Class Observer is the base class for all future observer's implementation.

The main points of interest are (2) and (4). Method 'subscribe' in Subject  
class receives parameter 'obs' as O, not as Observer. And method 'notify'  
in Observer class receive parameter 'sub' as S, not as Subject. But which  
type has 'this' at point (3) when Subject calls 'nofity' methods for each  
observers? One could expect that 'this' has type Subject. But if it is  
true, then calling 'notify' is illegal, since Observer.notify need S  
(which should be subclass of Subject), not Subject.

The trick is in point (1). The construct:

class Subject requires S

specifies that 'this' in Subject must have type S (or S subclass). Because  
of that call of 'notify' in point (3) is legal.

Example of use that SubjectObserver:

object SensorReader extends SubjectObserver {
   type S = Sensor;  /* (5) */
   type O = Display; /* (6) */
   abstract class Sensor extends Subject {
     val label: String;
     var value: double = 0.0;
     def changeValue(v: double) = {
       value = v;
       publish;
     }
   }
   class Display extends Observer {
     def println(s: String) = ...
     def notify(sub: Sensor) =
       println(sub.label + " has value " + sub.value);
   }
}

The concrete types for defining S and O are specified at points (5) and  
(6). Here Sensor class is an implementation of Subject, and Display is an  
implementation of Observer. Note that Display.notify receives Sensor, not  
Subject. And Sensor.subscribe (inherited from SubjectObserver#Subject)  
receives Display, not Observer. Because of that it is impossible to mix  
different subtypes of Subject/Observers:

object AccountNotificator extends SubjectObserver {
   type S = Account;
   type O = Notificator;
   class Account extends Subject { ... }
   class Notificator extends Observer { ... }
}

import SensorReader._
import AccountNotificator._

val sensor: new Sensor
val display: new Display
val account: new Account
val mailNotificator: new Notificator

sensor.subscribe( display ) // legal
account.subscribe( mailNotificator ) // legal
account.subscribe( display ) // ILLEGAL!!!

That error will be caught at compile time.

This approach is different from naive object-oriented implementation of  
Subject/Observer:

class Subject {
   private Observer[] observers_;
   void subscribe( Observer obs ) { observers_ ~= obj; }
   void publish() { foreach( obs; observers_ ) obs.notify( this ); } /* (7)  
*/
}
class Observer {
   abstract void notify( Subject ); /* (8) */
}

class Sensor : Subject { ... }
class Display : Observer {
   void notify( Subject sub ) { auto s = cast(Sensor)sub; ... } /* (9) */
}
class Account : Subject { ... }
class Notificator : Observer { ... }

auto sensor = new Sensor;
auto display = new Display;
auto account = new Account;
auto mailNotificator = new Notificator;

sensor.subscribe( display ); // Ok.
account.subscribe( mailNotificator ); // Ok.
account.subscribe( display ); // O-ops!

That error will be caught only at run-time.

I can implement Scala's solution in the current version of D via templates  
as:

template SubjectObserver(O,S)
   {
     abstract class Subject
       {
         private O[] observers_ = [];
         public void subscribe( O observer )
           {
             observers_ ~= observer;
           }
         public void publish()
           {
             foreach( o; observers_ )
               o.notify( self ); /* (10) */
           }
         abstract S self(); /* (11) */
       }

     abstract class Observer
       {
         abstract void notify( S subj );
       }
   }

alias SubjectObserver!(Display, Sensor) SensorReader;

class Sensor : SensorReader.Subject
   {
     private char[] label_;
     private double value_;

     this( char[] label, double value )
       {
         label_ = label.dup;
         value_ = value;
       }

     Sensor self() { return this; } /* (12) */

     char[] label() { return label_; }
     double value() { return value_; }
     void value( double v ) { value_ = v; publish; }
   }

class Display : SensorReader.Observer
   {
     override void notify( Sensor subj )
       {
         Stdout( subj.label )( " has value " )( subj.value ).newline;
       }
   }

void main()
   {
     auto s1 = new Sensor( "First", 0 );
     auto s2 = new Sensor( "Second", 0 );
     auto d1 = new Display;
     auto d2 = new Display;

     s1.subscribe( d1 ); s1.subscribe( d2 );
     s2.subscribe( d1 );

     s1.value = 2;
     s2.value = 3;
   }

But here it is necessary to have method 'self' (point (12)) and use 'self'  
instead of 'this' (point (11)).

It will be cool if D2.0 will allow something like:

template SubjectObserver(O,S)
   {
     abstract class Subject is S { /* (13) */
       ...
       void publish() {
          foreach( o; observers_ )
             o.notify( this ); /* (14) */
       }
   ...

The construction 'class Subject is S' (point (13)) tells compiler that  
'this' will have type 'S' (point (14)) instead of Subject. That  
construction (i.e. 'class A is B') should be applicable to classes inside  
templates only.

Disclaimer. Yes, I know that Sensor could mixin Subject, not inherit it.  
But I think there is probability that in some situation inheritance must  
be used by some very important reasons.

-- 
Regards,
Yauheni Akhotnikau



More information about the Digitalmars-d mailing list