[M3devel] Implementing the "Singleton pattern" in Modula-3

Mika Nystrom mika at async.caltech.edu
Tue Apr 7 07:39:17 CEST 2009


Hmm, well ... so in C++ and Smalltalk you can accomplish something
along these lines:

INTERFACE Singleton;

TYPE 
  T = SomeSuperTypeIReallyWantToSubClass OBJECT METHODS
    m();
  END;

END Singleton.

(****************************************)

MODULE Client;

BEGIN

   theSingleton := NEW(Singleton.T, m := MyM);
   theSingleton2 := NEW(Singleton.T, m := MyM);

END Client.

And by some magic involving overriding "new methods", you are
guaranteed that theSingleton and theSingleton2 are the same object...

In Modula-3 you can do something similar by providing an appropriate
init, but this can still get a bit hairy since the client can
override init and then you're back where you started.

As I said this is really quite a minor issue but it has annoyed me
a few times that I have to be very careful about allocating only one
of something, especially when it might be allocated from lots of
places and you want to make sure only one ever gets allocated in a single
program execution.  (E.g., various types of "registries"...)

     Mika

Tony Hosking writes:
>On 7 Apr 2009, at 11:18, Mika Nystrom wrote:
>
>> Well I think the problem is with this "is known to be an object  
>> type"...
>
>But if the opaque type T is T<:REFANY and the only revelation is  
>branded and private to a module (not exported by its interfaces) then  
>no-one can determine that it is an object type without forging the  
>BRAND.
>
>> You may in fact want to override the methods of the singleton, but
>> ensure that only one ever gets instantiated.
>>
>> It's a minor problem, but it does sometimes bother me that it is
>> difficult to prevent clients from NEWing pretty much anything they
>> like.
>>
>> By the way, the "Modula-3 Type System" paper also talks about a
>> nifty way to do narrowing very quickly (page 10, last paragraph).
>> I remember this is occasionally problematic.  (RTType.IsSubtype
>> isn't great.)  They speak of keeping an array of supertypes
>> for each type, and recording the "depth" (distance from REFANY)
>> of every type in, I guess, RT0.TypeDefn.   This seems moderately
>> space-efficient (better than a 2D array of all the typecodes),
>> and very fast.
>>
>>     Mika
>>
>> Tony Hosking writes:
>>> I would exploit opaque types and branding.  If T is opaque then you
>>> cannot instantiate T except in scopes where T's concrete type has  
>>> been
>>> revealed or is known to be an object type.  Thus, you could define an
>>> interface:
>>>
>>> GENERIC INTERFACE Singleton(Rep);
>>> TYPE T <: REFANY;
>>> VAR t: T;
>>> END Singleton.
>>>
>>> GENERIC MODULE Singleton(Rep);
>>> REVEAL T = BRANDED REF Rep.T;
>>> BEGIN
>>>  t := NEW(T);
>>> END Singleton.
>>>
>>> where Rep defines the type T.  You could do similar for object types.
>>>
>>>
>>> On 7 Apr 2009, at 06:31, Mika Nystrom wrote:
>>>
>>>>
>>>> Tony, Jay, you two seem to be most knowledgeable about this.
>>>>
>>>> Let's say I wanted to implement the "singleton pattern" in
>>>> Modula-3.  At present there seems to be no way of doing this.
>>>>
>>>> What about changing RTAllocator.callback as follows (or adding
>>>> another callback):
>>>>
>>>> VAR callback : PROCEDURE(r : REFANY) : REFANY := NIL;
>>>>
>>>> (* If non-NIL, the allocator calls "callback(r)" just before  
>>>> returning
>>>>  a new traced reference "r" and instead returns the result of
>>>> callback(r).
>>>>  See "RTAllocStats" for an example client.  It is a checked runtime
>>>> error
>>>>  for the typecode of r to differ from the typecode of the return
>>>> value of
>>>>  callback(r).  *)
>>>>
>>>>     Mika
>>>>
>>>>
>>>>



More information about the M3devel mailing list