Jump to page: 1 2
Thread overview
Selftype: an idea from Scala for D2.0
May 25, 2007
eao197
May 25, 2007
Henning Hasemann
May 25, 2007
eao197
May 26, 2007
David B. Held
May 26, 2007
eao197
May 26, 2007
eao197
May 26, 2007
eao197
May 29, 2007
eao197
May 27, 2007
Reiner Pope
May 27, 2007
eao197
May 27, 2007
Reiner Pope
May 27, 2007
eao197
May 27, 2007
Reiner Pope
May 27, 2007
eao197
May 25, 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
May 25, 2007
I must confess I didnt read your whole posting to the end, but it seems that this is much the same idea as I had in "Feature Request: interfaces declare types" plus some extra intelligence, or?

Henning

-- 
GPG Public Key: http://keyserver.veridis.com:11371/search?q=0x41911851 Fingerprint: 344F 4072 F038 BB9E B35D  E6AB DDD6 D36D 4191 1851
May 25, 2007
On Fri, 25 May 2007 21:48:10 +0400, Henning Hasemann <hhasemann@web.de> wrote:

> I must confess I didnt read your whole posting to the end,
> but it seems that this is much the same idea as I had in
> "Feature Request: interfaces declare types" plus some extra
> intelligence, or?

As I can see your idea is very close to 'abstract members' in Scala:

class Demo {
  type A // This member must be defined in a derived class.
            // There isn't any restruction to type A.

  type B <: Another // This is another member which must be
                            // defined in a derived class.

  /* ... In the body of class Demo names A and B can be
          used as template parameters in D templates ... */
}

This looks similar to your proposal:

interface ContainerIterator(T) {
  T content();
}

interface Container(T) {
  type Iterator : ContainerIterator!(T); // Like Scala's 'type Iterator <: ContainerIterator[T]'

  T first();
  void remove(T);
  Iterator(T) firstIterator();
  void remove(Iterator(T));
}

JFYI: There is a citation from 'Scalable Component Abstractions':
\begin{quote}
...
Abstract type members provide a fexible way to abstract over concrete types of components. Abstract types can hide information about internals of a component, similar to their use in SML signatures. In an object-oriented framework where classes can be extended by inheritance, they may also be used as a fexible means of parameterization (often called family polymorphism [11]).
...
[11] E. Ernst. Family polymorphism. In Proceedings of the European Conference on Object-Oriented Programming, pages 303-326, Budapest, Hungary, 2001.
\end{quote}

So it seems that you reinvent the idea about 'abstract type members' ;)

But I speak about another Scala's feature: selftypes. For example:

template Demo(S) {
  class Dumpable {
    void dump(S stream) {
      stream.put( this ); // At this point 'this' is MyClass.
    }
  }
}

My proposal is to tell the compiler that 'this' in MyClass has type T or any of its subclass:

template Demo(S, T) {
  class Dumpable is T {
    void dump(S stream) {
      stream.put( this ); // At this point 'this' is T.
    }
  }
}

class MyStream {
  void put( MyClass obj ) { ... }
}

class MyClass : Demo!( MyStream, MyClass ).Dumpable {
  ...
}

auto myStream = new MyStream;
auto my = new MyClass;
my.dump( myStream );  // Method MyStream.put(MyClass) must be called.

-- 
Regards,
Yauheni Akhotnikau
May 25, 2007
eao197 wrote:

> 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) */
>    }
> }

I can see how this removes the need for "unsafe" casts. However, it might cause some extra trouble to the planned reflection support :) From what I've read and heard, Scala does not yet support reflection.

Can you show how this would work if there were multiple subclasses
of 'Subject' and 'Observer'? (for example multiple sensor types in the
SensorReader example) Does it require language support for multiple
dispatch then?

May 26, 2007
eao197 wrote:
> On Fri, 25 May 2007 21:48:10 +0400, Henning Hasemann <hhasemann@web.de> wrote:
> [...]
> class Demo {
>   type A // This member must be defined in a derived class.
>             // There isn't any restruction to type A.
> 
>   type B <: Another // This is another member which must be
>                             // defined in a derived class.
> 
>   /* ... In the body of class Demo names A and B can be
>           used as template parameters in D templates ... */
> }

Your use case is a form of metatyping.  In the Subject/Observer example, you notice that the types S and O may be template parameters, but you want to restrict their type to be subtypes of the nested Subject/Observer types.  That is, you want to provide metatypes for the S and O metaparameters.

> This looks similar to your proposal:
> 
> interface ContainerIterator(T) {
>   T content();
> }
> 
> interface Container(T) {
>   type Iterator : ContainerIterator!(T); // Like Scala's 'type Iterator <: ContainerIterator[T]'
> 
>   T first();
>   void remove(T);
>   Iterator(T) firstIterator();
>   void remove(Iterator(T));
> }

And this is also a form of metatyping.  In this case, we want to declare that the dependent type Iterator(T) has a metatype of ContainerIterator(T) (that is, we minimize the set of possible values (types) that Iterator(T) can have by defining the nature of those values in terms of a metafunction...or, to put it another way, Iterator(T) is an algebraic metatype).

> JFYI: There is a citation from 'Scalable Component Abstractions':
> \begin{quote}
> [...]
> So it seems that you reinvent the idea about 'abstract type members' ;)

Or rather, the CS community does not have a unified notion of metatyping and is inventing terms as they go.

> [...]
> My proposal is to tell the compiler that 'this' in MyClass has type T or any of its subclass:
> 
> template Demo(S, T) {
>   class Dumpable is T {
>     void dump(S stream) {
>       stream.put( this ); // At this point 'this' is T.
>     }
>   }
> }
> 
> class MyStream {
>   void put( MyClass obj ) { ... }
> }
> 
> class MyClass : Demo!( MyStream, MyClass ).Dumpable {
>   ...
> }
> [...]

This particular example isn't very motivating because you would typically just do this:

void dump(T)(S stream, T obj)
{
    stream.put(obj);
}

In general, this is equivalent to the CRTP in C++, which has the general form in D of:

class Derived : RecurringBase!(Derived)
{ ... }

As with any recurrence pattern, you have to be careful about what is allowed and what isn't, or you may end up with an infinitely nested definition.  For instance, if RecurringBase(T) is allowed to derive from T, you get the regress.  However, D prevents this because T is a forward reference at instantiation time.

I'm not sure what the value-add of the "self type" is when the CRTP is available in D:

class Base(T) {	T x; }
class Derived : Base!(Derived) { int x; }
void main() { Derived d; }

This is a perfectly valid D program according to dmd.

Dave
May 26, 2007
On Sat, 26 May 2007 08:21:48 +0400, David B. Held <dheld@codelogicconsulting.com> wrote:

> In general, this is equivalent to the CRTP in C++, which has the general form in D of:
>
> class Derived : RecurringBase!(Derived)
> { ... }
>
> As with any recurrence pattern, you have to be careful about what is allowed and what isn't, or you may end up with an infinitely nested definition.  For instance, if RecurringBase(T) is allowed to derive from T, you get the regress.  However, D prevents this because T is a forward reference at instantiation time.
>
> I'm not sure what the value-add of the "self type" is when the CRTP is available in D:
>
> class Base(T) {	T x; }
> class Derived : Base!(Derived) { int x; }
> void main() { Derived d; }
>
> This is a perfectly valid D program according to dmd.

Could you rewrite my initial example with Subject/Observer with using CRTP technique?

-- 
Regards,
Yauheni Akhotnikau
May 26, 2007
On Sat, 26 May 2007 00:30:11 +0400, Jari-Matti Mäkelä <jmjmak@utu.fi.invalid> wrote:

>> 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) */
>>    }
>> }
>
> I can see how this removes the need for "unsafe" casts. However, it might
> cause some extra trouble to the planned reflection support :) From what
> I've read and heard, Scala does not yet support reflection.

I'm not a Scala programmer so I don't know what the current situation with refelection is.
But some support of refelection exits: http://www.scala-lang.org/docu/files/api/scala/reflect$content.html

> Can you show how this would work if there were multiple subclasses
> of 'Subject' and 'Observer'? (for example multiple sensor types in the
> SensorReader example) Does it require language support for multiple
> dispatch then?

Do you what to see Scala example?

-- 
Regards,
Yauheni Akhotnikau
May 26, 2007
eao197 wrote:
> On Sat, 26 May 2007 00:30:11 +0400, Jari-Matti Mäkelä <jmjmak@utu.fi.invalid> wrote:
> 
>>> 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) */
>>>    }
>>> }

>> Can you show how this would work if there were multiple subclasses
>> of 'Subject' and 'Observer'? (for example multiple sensor types in the
>> SensorReader example) Does it require language support for multiple
>> dispatch then?
> 
> Do you what [sic] to see Scala example?

Yes, please :)
May 26, 2007
eao197 wrote:

> On Sat, 26 May 2007 08:21:48 +0400, David B. Held <dheld@codelogicconsulting.com> wrote:
> 
>> In general, this is equivalent to the CRTP in C++, which has the general form in D of:
>>
>> class Derived : RecurringBase!(Derived)
>> { ... }
>>
>> As with any recurrence pattern, you have to be careful about what is allowed and what isn't, or you may end up with an infinitely nested definition.  For instance, if RecurringBase(T) is allowed to derive from T, you get the regress.  However, D prevents this because T is a forward reference at instantiation time.
>>
>> I'm not sure what the value-add of the "self type" is when the CRTP is available in D:
>>
>> class Base(T) {      T x; }
>> class Derived : Base!(Derived) { int x; }
>> void main() { Derived d; }
>>
>> This is a perfectly valid D program according to dmd.
> 
> Could you rewrite my initial example with Subject/Observer with using CRTP technique?

You're using it already in the first post. The two classes are packaged inside a single template so it might not seem so obvious.
May 26, 2007
On Sat, 26 May 2007 14:28:27 +0400, Jari-Matti Mäkelä <jmjmak@utu.fi.invalid> wrote:

>>> Can you show how this would work if there were multiple subclasses
>>> of 'Subject' and 'Observer'? (for example multiple sensor types in the
>>> SensorReader example) Does it require language support for multiple
>>> dispatch then?
>>
>> Do you what [sic] to see Scala example?

Sorry, it is a typo. I mean: 'Do you want to see Scala example?'

>
> Yes, please :)

It is not a simple task for me, because I'm not a Scala programmer. I've tried to make a sample today but without  success. So I ask my friends to help me with it. Now I'm waiting their replies. So I hope to publish such example some time later.

-- 
Regards,
Yauheni Akhotnikau
« First   ‹ Prev
1 2