[Oberon] Protocols (interfaces) in Oberon-2
Luca Boasso
luke.boasso at gmail.com
Tue Oct 27 16:07:09 CET 2020
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
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.inf.ethz.ch/pipermail/oberon/attachments/20201027/eb0d60a8/attachment-0001.html>
More information about the Oberon
mailing list