[Oberon] Protocols (interfaces) in Oberon-2

Luca Boasso luke.boasso at gmail.com
Tue Oct 27 16:25:36 CET 2020


I wrote the reasoning in the comments of the module I shared.
Maybe my explanation does not convince you on the purpose of all this.
You could find a better rationale here:
https://spf13.com/post/is-go-object-oriented/
Also, perhaps this use case in the Go language can help understanding why
structural type compatibility can be useful :
https://blog.golang.org/json-rpc

On Tue, Oct 27, 2020 at 10:09 AM Jörg <joerg.straube at iaeth.ch> wrote:

> Why?
>
> Jörg
>
>
>
> *Von: *Oberon <oberon-bounces at lists.inf.ethz.ch> im Auftrag von Luca
> Boasso <luke.boasso at gmail.com>
> *Antworten an: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
> *Datum: *Dienstag, 27. Oktober 2020 um 16:08
> *An: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
> *Betreff: *Re: [Oberon] Protocols (interfaces) in Oberon-2
>
>
>
> The problem is not the syntax but the semantic.
>
> This should be allowed:
>
>
>
>       VAR i: I; r: R;
>         inc: Incrementer;
>
>       inc := i;
>       inc := r;
>
>
>
>
>
> On Tue, Oct 27, 2020 at 10:02 AM Jörg <joerg.straube at iaeth.ch> wrote:
>
> Luca
>
>
>
> I fully agree that with your proposed INTERFACE syntax the setup of the
> environment is much easier.
>
> My proposed MODULE structure is more cumbersome to setup.
>
>
>
> After you set up everything (implemented all needed interface
> procedures…), is there a big difference whether you write
>
> in your syntax
>
>   NEW(i);
>   i.h := 3;
>
>   inc := i;
>   inc.IncBy2;
>
>
>
> or in my syntax
>
>  New(i);
> i.h := 3;
>
> i.inc.IncBy2(i);
>
>
>
> br
>
> Joerg
>
>
>
> *Von: *Oberon <oberon-bounces at lists.inf.ethz.ch> im Auftrag von Luca
> Boasso <luke.boasso at gmail.com>
> *Antworten an: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
> *Datum: *Dienstag, 27. Oktober 2020 um 15:37
> *An: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
> *Betreff: *Re: [Oberon] Protocols (interfaces) in Oberon-2
>
>
>
> Although this is a solution to the problem, it doesn't buy you much
> because all generic signatures must have the type *Data.Any*.
>
> To lift the limitation that the clients must know about the module *Data*
> we could extend Oberon-07 to support Component Pascal's ANYPTR type,
> defined as
>
>
>
> ANYREC and ANYPTR
> Each base record is implicitly regarded as an extension of the new
> abstract standard type ANYREC,
> even if it is declared without explicit base type. ANYREC is an empty
> record that forms the root of all
> record type hierarchies. ANYPTR is a new standard type that corresponds to
> a POINTER TO
> ANYREC.
> These new types make it easier to achieve interoperability between
> independently developed
> frameworks, by allowing completely generic parameters.
>
>
>
> The following pseudo definitions can be assumed:
> ANYREC = ABSTRACT RECORD END;
> ANYPTR = POINTER TO ANYREC;
> PROCEDURE (a: ANYPTR) FINALIZE-, NEW, EMPTY;
>
>
>
> But regardless, the beauty of interfaces is to be able to do something
> like the following:
>
>
>
> MODULE Demo;
>   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
>         PROCEDURE ToString* (VAR a: ARRAY OF CHAR) ;
>     END ;
>
>     Incrementer* = INTERFACE
>       PROCEDURE IncBy2*();
>     END ;
>
>   PROCEDURE ( i : I ) ToString* (VAR a: ARRAY OF CHAR) ;
>   BEGIN a := "integer"
>   END ToString;
>
>   PROCEDURE ( r : R ) ToString* (VAR a: ARRAY OF CHAR) ;
>   BEGIN a := "real"
>   END ToString;
>
>   PROCEDURE ( i : I ) IncBy2*;
>   BEGIN i.h := i.h + 2
>   END IncBy2;
>
>   PROCEDURE ( r : R ) IncBy2*;
>   BEGIN  r.h := r.h + 2.0
>   END IncBy2;
>
>   PROCEDURE CallStringer(s: Stringer);
>       VAR t: ARRAY 32 OF CHAR
>   BEGIN
>       s.ToString(t);
>       Texts.WriteString(W,t);
>   END CallStringer;
>
>   PROCEDURE Test*;
>       VAR i: I; r: R; ;
>         s: Stringer;
>         inc: Incrementer;
>   BEGIN
>       NEW(i); NEW(r);
>       i.h := 3;
>       r.h := 7.5;
>       (*
>         The assignment below allows for structural type compatibility
> between a
>         record (i) and an interface (s). Interfaces can only have
> PROCEDUREs in
>         their definition, if a record implements the set of PROCEDUREs of
> a
>         interface, it can then be assigned to it.
>         Any record implements the empty interface: INTERFACE END;
>         This is how the Go language works.
>       *)
>       inc := i;
>       inc.IncBy2;
>       inc := r;
>       inc.IncBy2;
>       CallStringer(i);  (* this is an assignment to the formal parameter*)
>
>       CallStringer(r);  (* this is an assignment to the formal parameter*)
>
>       (*
>         Alternatively we could still allow for nominal type compatibility
>         forcing the user to specify that a record implements a set of
>         interfaces:
>
>           I* = POINTER TO IDesc;
>           IDesc* = RECORD(Stringer, Incrementer)
>               h: INTEGER
>           END ;
>
>         The fact that a record implements an interface is explicit. This is
>         how Java does it. Extending from multiple interface types does not
>         bring the complexity and pitfalls of multiple inheritance.
>         To simplify the language in both cases, structural or nominal type
>         compatibility, we should drop the ability to extend RECORDs with
> other
>         records. Only INTERFACEs are allowed because we want only to
> extend
>         behavior not data. If you also need the data simply add compose
>         records as fields.
>       *)
>   END Test;
> END Demo.
>
>
>
>
>
> On Mon, Oct 26, 2020 at 2:29 PM Charles Perkins <chuck at kuracali.com>
> wrote:
>
> Hmm...
>
>
>
> Everything that implements an interface must be a descendant of Module
> Data but Data is empty, therefore its signature never needs to change. The
> use of an interface is checked in the CASE statement and cast there to the
> interface needed by the client. I think multiple interfaces can be
> implemented by successively extending Data. This could do it!
>
>
>
> I am going to try to use this, thank you Jörg for your patience with me.
>
>
>
> Cheers,
>
> Chuck
>
>
>
> On Mon, Oct 26, 2020 at 11:42 AM Jörg <joerg.straube at iaeth.ch> wrote:
>
> Chuck
>
>
>
> Okay. My proposal in standard Oberon-07 for your new requirement is this:
>
>
>
> MODULE Data;
>
> TYPE
>
>   Any* = POINTER TO Empty;
>
>   Empty* = RECORD END;
>
> END Data.
>
>
>
> MODULE Jsonify;
> IMPORT Data;
> TYPE
>   Methods* = POINTER TO MDesc;
>   MDesc* = RECORD
>     toJSON: PROCEDURE(this: Data.Any);
>   END;
> END Jsonify.
>
> MODULE myData;
>
> IMPORT Data;
>
> TYPE
>
>   Tree* = POINTER TO TreeDesc;
>
>   TreeDesc* = RECORD (Data.Empty)
>
>     left, right: Tree;
>
>     j: Jsonify;
>
>   END;
>
> VAR t: Tree;
>
> PROCEDURE J(t: Data.Any);
>
>   BEGIN
>
>     CASE t OF Tree: (* implement here your Tree-specific JSON routine *)
>
>     END
>
>   END J;
>
> PROCEDURE New*(VAR t: Tree); BEGIN NEW(t); Jsonify.New(t.j); t.j.toJSON :=
> J END New
>
> BEGIN
>
>  New(t); t.j.toJSON(t)
>
> END myData.
>
>
>
> br
>
> Jörg
>
>
>
> *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: *Montag, 26. Oktober 2020 um 17:03
> *An: *ETH Oberon and related systems <oberon at lists.inf.ethz.ch>
> *Betreff: *Re: [Oberon] Protocols (interfaces) in Oberon-2
>
>
>
> Hi Jörg and other interested people,
>
>
>
> There is just one piece missing I think for the usage of Interfaces that I
> am looking for. I would like to be able to write a routine that doesn't
> know what Data is (in the provided example) but can still hold a pointer to
> it or to anything else that implements the JSONify interface (and not
> anything that doesn't implement the JSONify interface) and can call it's
> ToJSON procedure.
>
>
>
> In my previous reply I incorrectly thought that a record in Oberon can be
> an extension of two base records, which is not right, and not what Jörg is
> presenting.
>
>
>
> Apologies for my confusion,
>
> Chuck
>
>
>
>
>
> On Mon, Oct 26, 2020 at 7:37 AM Charles Perkins <chuck at kuracali.com>
> wrote:
>
> Hi Jörg,
>
>
>
> With this example I think I understand how Oberon can in fact already do
> what I want in composing Interfaces. In Wirthian fashion the mechanism is
> explicit rather than implicit. I had not made the mental jump to how a
> record may be based on more than one base record... with that, everything
> else falls into place.
>
>
>
> I observe that this means that interfaces are 'opt in' rather than
> automatic based on just matching a subset of method names, but that's
> typical and arguably more safe anyway.
>
>
>
> I'm going to think on this some more.
>
>
>
> Thank you very much for taking the time to illustrate this to us.
>
>
>
> Best,
>
> Chuck
>
>
>
>
>
> On Sun, Oct 25, 2020 at 11:55 PM Jörg <joerg.straube at iaeth.ch> wrote:
>
> Chuck
>
>         > Andreas, in your scheme can you create separate protocols, for
> example
>         > "Jsonify" with the method ToJSON and a different protocol
> "Persistify" with
>         > the methods "Store" and "Load", and have other records implement
> one or the
>         > other or neither or both?
>
> Here my proposal for doing this in standard Oberon-07.
> In the previous mail, I separated interface/protocol and implementation
> for clarity. The same should be done here. For brevity, I combined the two
> here.
>
> MODULE Data;
> (* Definition of whatever your internal data structure looks like. Here
> just an example *)
> TYPE
>   Tree* = POINTER TO TreeDesc;
>   TreeDesc* = RECORD val*: ARRAY 15 OF CHAR; left*, right*: Tree END
> END Data.
>
> MODULE Jsonify;
> IMPORT Data;
> TYPE
>   Methods* = POINTER TO MDesc;
>   MDesc* = RECORD
>     toJSON: PROCEDURE(this: Data.Tree);
>   END;
>   (* empty or default implementation *)
>   PROCEDURE J(this: Data.Tree); END J;
>   PROCEDURE New*(VAR m: Method); BEGIN NEW(m); m.toJSON := J END;
> END Jsonify.
>
> MODULE Persistify;
> IMPORT Data;
> TYPE
>   Methods* = POINTER TO MDesc;
>   MDesc* = RECORD
>     Load: PROCEDURE(VAR this: Data.Tree);
>     Store: PROCEDURE(this: Data.Tree)
>   END;
>   (* empty or default implementation *)
>   PROCEDURE L(VAR this: Data.Tree); BEGIN this := NIL END L;
>   PROCEDURE S(this: Data.Tree); END S;
>   PROCEDURE New*(VAR m: Methods); BEGIN NEW(m); m.Load := L; m.Store := S
> END Init;
> END Persistify.
>
> Here now a module using both interfaces/protocols and overwrite even one
> persist procedure with an own version, if wanted.
> MODULE Usage;
> IMPORT Data, Jsonify, Persistify;
> TYPE
>   User = RECORD (Data.Tree)
>     j: Jsonify;
>     p: Persistify
>   END;
> VAR u: User;
> PROCEDURE myLoad(this: Data.Tree); (* implement your version of persist
> Load *) END myLoad;
> PROCEDURE New*(VAR u: User);
>   BEGIN NEW(u); Jsonify.New(u.j); Persistify.New(u.p); u.p.Load := myLoad
> END New;
>
> BEGIN
>   New(u); u.j.toJSON(u); u.p.Load(u) (* this calls my version *)
> END Usage.
>
> Adding the qualifiers "j" and "p" circumvents the ambiguity in case the
> two interfaces defined methods with the same name.
>
> br
> Jörg
>
>
>
> --
> 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
>
> --
> 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/20201027/d2263be6/attachment.html>


More information about the Oberon mailing list