[Oberon] Protocols (interfaces) in Oberon-2

Luca Boasso luke.boasso at gmail.com
Sun Oct 25 18:52:39 CET 2020


I agree with Chuck, interfaces are useful to avoid the issues of
implementation inheritance all together and use composition instead.
This has been explored by former ETH PhDs with the language Lagoona, see On
Reconciling Objects, Components,and Efficiency in Programming Languages
<http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.76.8656&rep=rep1&type=pdf>
for rationale and implementation techniques (the language Emerald follows a
similar approach)

On Sun, Oct 25, 2020 at 11:15 AM Charles Perkins <chuck at kuracali.com> wrote:

> Hi Jörg,
>
>
> If I understand your example correctly, the Interface procedures are
> declared in the base "Dynamic" module while their implementation resides in
> a derivative, potentially separately compiled record definition. This is a
> useful pattern for a self-contained project. It requires you to make every
> interface implementation a descendant of one record in the "Dynamic"
> however. Every time you add a new interface you have to recompile the
> dynamic module and everything that uses it, because the symbol file has
> changed. In addition, different developers may be adding interfaces
> independently. Either everyone is making changes to the same "Dynamic"
> module and their changes have to be merged, or each developer has a
> different "Dynamic" (or otherwise-named) module and their interfaces do not
> compose.
>
> I would like to be able to request the same behavior from two completely
> independent types, and if the interfaces match, perform the function call.
> The idea I'm going for is something that will allow you to make, for
> example, an interface that will print anything with a "printer" method, a
> different interface that will serialize to JSON anything with a "ToJSON"
> method, etc.
>
> Philosophically though I think it's the same question of why have Oberon-2
> with type-bound procedures when the same functionality can be achieved with
> plain Oberon-07 and procedure variables?  Type-bound procedures introduce
> complexity in the compiler and module formats that are not strictly
> necessary.
>
> Just as Oberon-2 is not the same language as Oberon, I think a language
> that has this 'dynamic trait' capability should be called something else,
> like perhaps Oberon-2i.
>
> My two cents...
> Chuck
>
>
> On Sun, Oct 25, 2020 at 8:24 AM Jörg <joerg.straube at iaeth.ch> wrote:
>
>> Chuck
>>
>>
>>
>> In plain Oberon-07, you could solve this as follows: Not the nicest
>> syntax but it seems doable.
>>
>>
>>
>> MODULE Dynamic;
>>
>> TYPE
>>
>>   Obj* = POINTER TO Desc END
>>
>>   Desc* = RECORD
>>
>>     toString*: PROCEDURE (o: Obj; VAR a: ARRAY OF CHAR)
>>
>>   END
>>
>> END Dynamic.
>>
>>
>>
>> MODULE Int;
>>
>> IMPORT Dynamic;
>>
>> TYPE
>>
>>   Obj* = POINTER TO Desc;
>>
>>   Desc* = RECORD (Dynamic.Desc)
>>
>>     val: INTEGER
>>
>>   END;
>>
>>
>>
>> PROCDEURE I2S(o: Obj; VAR a: ARRAY OF CHAR); BEGIN a:=”integer” END I2S;
>>
>> PROCEDURE New*(VAR o: Obj; i: INTEGER); BEGIN NEW(o); o.toString := I2S;
>> o.val := i END New;
>>
>>
>>
>> END Int.
>>
>>
>>
>> MODULE Real;
>>
>> IMPORT Dynamic;
>>
>> TYPE
>>
>>   Obj* = POINTER TO Desc;
>>
>>   Desc* = RECORD (Dynamic.Desc)
>>
>>     val: REAL
>>
>>   END;
>>
>>
>>
>> PROCDEURE R2S(o: Obj; VAR a: ARRAY OF CHAR); BEGIN a:=”real” END R2S;
>>
>> PROCEDURE New*(VAR o: Obj; r: REAL); BEGIN NEW(o); o.toString := R2S;
>> o.val := r END New;
>>
>>
>>
>> END Real.
>>
>>
>>
>> MODULE Test;
>>
>> IMPORT Dynamic, Int, Real, Out;
>>
>> VAR i: Int.Obj; r: Real.Obj; dyn: Dynamic.Obj;
>>
>> BEGIN
>>
>>   Int.New(i, 3); dyn := i;
>>
>>  dyn.asString(dyn, a); Out.String(a); Out.Ln;
>>
>>   Real.New(r, 3.14); dyn := r;
>>
>>   dyn.asString(dyn, a); Out.String(a); Out.Ln;
>>
>> END Test.
>>
>>
>>
>>
>>
>> *Von: *Oberon <oberon-bounces at lists.inf.ethz.ch> im Auftrag von Charles
>> Perkins <chuck at kuracali.com>
>> *Antworten an: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
>> *Datum: *Sonntag, 25. Oktober 2020 um 16:03
>> *An: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
>> *Betreff: *Re: [Oberon] Protocols (interfaces) in Oberon-2
>>
>>
>>
>> Apologies for this : " x0, x1, u: REAL;" was a fragment that snuck in
>> from testing. I have the above code successfully parsing in a fork of
>> Andreas's Extended Oberon compiler. I don't have the method table
>> generation code working yet. It turns out that the Run-time needs to have
>> symbolic type information available, which could be simply loading the smb
>> file alongside the rsc file for code that uses interfaces, or it could
>> involve embedding a hash of the name and parameters of the type-bound
>> procedure in another section of the rsc file. I haven't decided yet.
>>
>>
>>
>> On Sun, Oct 25, 2020 at 7:33 AM Charles Perkins <chuck at kuracali.com>
>> wrote:
>>
>> I think Interfaces / Protocols / Dynamic Traits (what Rust calls them)
>> would be a quite useful extension to Oberon. I'm looking at doing it a
>> different way, like this:
>>
>>
>>   VAR W: Texts.Writer;
>>
>>
>>
>>   TYPE
>>        I* = POINTER TO IDesc;
>>        IDesc* = RECORD
>>             h: INTEGER
>>        END ;
>>
>>        R* = POINTER TO RDesc;
>>        RDesc* = RECORD
>>             h: REAL
>>        END ;
>>
>>        Stringer* = INTERFACE OF
>>             PROCEDURE String* (VAR a: ARRAY OF CHAR) ;
>>        END ;
>>
>>
>>   PROCEDURE ( i : I ) String* (VAR a: ARRAY OF CHAR) ;
>>   BEGIN a := "integer"
>>   END String;
>>
>>   PROCEDURE ( r : R ) String* (VAR a: ARRAY OF CHAR) ;
>>   BEGIN a := "real"
>>   END String;
>>
>> In the above scheme an Interface looks just like a collection of
>> type-bound procedure definitions with no bodies.
>>
>>
>>
>> The trick is when it comes time to use the interface, which is when the
>> code needs to know which actual procedure to call based on the record type
>> assigned to it during execution. The record type assigned to an interface
>> could be any record that contains the String type-bound procedure (in this
>> case.) It might be the first method, or the third, or the sixth... Go
>> solves this by generating a dispatch table for the Interface when a type is
>> assigned to it.
>>
>>
>>
>> In Oberon that table-making routine could be satisfied by adding another
>> Trap condition in Kernel.Trap much like how New is implemented.
>>
>>
>>
>>   PROCEDURE Test*;
>>       VAR i: I; r: R; t: ARRAY 32 OF CHAR;
>>         s,s2: Stringer;
>>         x0, x1, u: REAL;
>>
>>   BEGIN
>>       NEW(i); NEW(r);
>>
>>       i.h := 3;
>>       r.h := 7.5;
>>       s := i;
>>
>>       s.Stringer(t);
>>
>>       Texts.WriteString(W,t);
>>       s := r;
>>
>>       s.Stringer(t);
>>       Texts.WriteString(W,s);
>>
>>    END Test;
>>
>> The above idea for Interfaces builds on the mechanisms already in place
>> in the Oberon-2 compiler and run-time. I think it would be quite useful for
>> allowing a program to choose from multiple implementations of an interface
>> without constraining them to derive from the same base type while still
>> keeping strong static typing and separate linking and loading.
>>
>>
>>
>> Chuck
>>
>>
>>
>> On Sun, Oct 25, 2020 at 6:27 AM Luca Boasso <luke.boasso at gmail.com>
>> wrote:
>>
>> A key feature of protocols / interfaces is the safe multiple inheritance:
>> you can explicitly or implicitly (like in the Go language) implement
>> several interfaces and be type compatible with each one of them.
>>
>>
>>
>> Do you support something like the following?
>>
>>
>>
>> TextDesc = RECORD (TextProtocol.TextDesc, WriteProtocol.WriterDesc) END
>> ;  (*this means: “implements TextProtocol.TextDesc AND
>> WriteProtocol.WriterDesc "*)
>>
>>
>>
>> If this is not supported I don't see this feature being that useful. To
>> support the feature above the implementation is more complicated than
>> Oberon-2's bound procedures. See https://research.swtch.com/interfaces
>> for one way of doing this, or "Efficient implementation of Java
>> interfaces: Invokeinterface considered harmless"
>> <http://www.academia.edu/download/42084165/Efficient_Implementation_of_Java_Interfa20160204-28309-28q4h3.pdf>
>>
>>
>>
>> On Sun, Oct 25, 2020 at 6:46 AM Andreas Pirklbauer <
>> andreas_pirklbauer at yahoo.com> wrote:
>>
>> Correction: In Text1, it’s TextDesc = RECORD (TextProtocol.TextDesc) of
>> course
>>
>> —————————
>>
>> Protocols (sometimes called interfaces) can be added to
>> Oberon-2 without adding any keywords to the language.
>>
>> This is one of the key differences to how it is usually defined
>> and implemented, e.g. in Swift [*] or in Integrated Oberon [**]
>>
>> Under the new minimalistic design, what distinguishes a protocol
>> from an actual implementation (of the class) is that in the protocol
>> definition the implementations of the class methods are simply not
>> defined. Instead, any module that *imports* a protocol definition
>> can “adopt” (i.e. implement) it. See the example below.
>>
>> An experimental implementation showed that if the language
>> is extended in *this* way, the implementation cost is minimal.
>>
>> But the question is: Is it worth it? Simplicity of implementation
>> should of course not be a criteria for adopting a new feature.
>>
>> Personally, I am rather sceptical of the usefulness of protocols.
>> But perhaps someone provides a good reason to adopt them.
>>
>> -ap
>>
>>
>> Example:
>>
>>   MODULE TextProtocol;  (*protocol definition*)
>>     TYPE Text = POINTER TO TextDesc;
>>       TextDesc = RECORD data*: (*text data*) END ;
>>       PROCEDURE (t: Text) Insert (string: ARRAY OF CHAR; pos: LONGINT);
>>       PROCEDURE (t: Text) Delete (from, to: LONGINT);
>>       PROCEDURE (t: Text) Length (): LONGINT;
>>   END TextProtocol;
>>
>>   MODULE Text1; (*one implementation of the Text protocol*)
>>     IMPORT TextProtocol;
>>     TYPE Text = POINTER TO TextDesc;
>>       TextDesc = RECORD (TextProtocol.TextDesc) END ;  (*this means:
>> “implements TextProtocol.TextDesc"*)
>>
>>     PROCEDURE (t: Text) Insert (string: ARRAY OF CHAR; pos: LONGINT);
>>     BEGIN (*implementation of Insert*)
>>     END Insert;
>>
>>     PROCEDURE (t: Text) Delete (from, to: LONGINT);
>>     BEGIN (*implementation of Delete*)
>>     END Delete;
>>
>>     PROCEDURE (t: Text) Length (): LONGINT;
>>     BEGIN (*implementation of Length*)
>>     END Insert;
>>   END Text1;
>>
>>
>>   MODULE Text2; (*another implementation of the Text protocol*)
>>     IMPORT TextProtocol;
>>     TYPE Text = POINTER TO TextDesc;
>>       TextDesc = RECORD (TextProtocol.TextDesc) END ;  (*this means:
>> “implements TextProtocol.TextDesc"*)
>>
>>     PROCEDURE (t: Text) Insert (string: ARRAY OF CHAR; pos: LONGINT);
>>     BEGIN (*implementation of Insert*)
>>     END Insert;
>>
>>     PROCEDURE (t: Text) Delete (from, to: LONGINT);
>>     BEGIN (*implementation of Delete*)
>>     END Delete;
>>
>>     PROCEDURE (t: Text) Length (): LONGINT;
>>     BEGIN (*implementation of Length*)
>>     END Insert;
>>   END Text2;
>>
>>
>> [*] https://docs.swift.org/swift-book/LanguageGuide/Protocols.html#
>> <https://docs.swift.org/swift-book/LanguageGuide/Protocols.html>
>> [**] https://github.com/io-core/technotes/blob/main/technote014.md
>>
>> --
>> Oberon at lists.inf.ethz.ch mailing list for ETH Oberon and related systems
>> https://lists.inf.ethz.ch/mailman/listinfo/oberon
>>
>> --
>> Oberon at lists.inf.ethz.ch mailing list for ETH Oberon and related systems
>> https://lists.inf.ethz.ch/mailman/listinfo/oberon
>>
>> -- Oberon at lists.inf.ethz.ch mailing list for ETH Oberon and related
>> systems https://lists.inf.ethz.ch/mailman/listinfo/oberon
>> --
>> Oberon at lists.inf.ethz.ch mailing list for ETH Oberon and related systems
>> https://lists.inf.ethz.ch/mailman/listinfo/oberon
>>
> --
> Oberon at lists.inf.ethz.ch mailing list for ETH Oberon and related systems
> https://lists.inf.ethz.ch/mailman/listinfo/oberon
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.inf.ethz.ch/pipermail/oberon/attachments/20201025/f220dd22/attachment-0001.html>


More information about the Oberon mailing list