Alternative zu interfaces bei OOP



  • So sehen interfaces konzeptionell in java, c++ und c# aus:

    interface A
    {
        void f();
        void f( int i );
    }
    
    class B implements A  //warum muss man jetzt schon "implements A" sagen?
    {
        void f()
        {
            //...
        }
        void f( int i )
        {
            //...
        }
    }
    
    class C
    {
        void f( A a )
        {
        }
    }
    
    //...
    
    C.do( new B() );
    

    Wäre das hier nicht schöner?

    class A
    {
         void f()
         {
         }
         void f( int )
         {
         }
    }
    
    interface B
    {
        void f();
        void f( int );
    }
    
    class C
    {
        void f( B b )
        {
             //...
        }
    }
    C.do( new A() );  //A erfüllt das interface B -> kann der Methode übergeben werden
    

    Intern könnte sowas ja eigentlich ganz einfach umgesetzt werden, indem eine Referenz auf B einfach einen data pointer auf den Objektspeicher und einen vtable pointer hat. Beim Übergeben eines A Objekts wird dann statisch überprüft, ob A das interface erfüllt und gegenfalls ein neuer vtable erstellt/ein schon erstellter benutzt.
    Das hätte den Vorteil, dass man nicht beim design der Klasse nachdenken muss, welche interfaces eine Klasse jetzt genau bedienen sollte. So könnte man in c++ z.B. container polymorph verwenden, ohne dass diese für Polymorphie gemacht sein müssen, d.h. auch keine vtable pointer mit sich rummschleppen müssen.
    Wird sowas schon in einer Sprache verwendet, und wenn ja, warum nicht von der c++ Familie? Gibt es starke Punkte, die gegen dieses Sprachmittel sprechen?



  • Woher weiß B, welche Funktion aufgerufen werden muss?



  • Nennt sich template.



  • Auf der englischen Wikipedia wird der Unterschied zwischen nominal subtyping und structural subtyping erklärt.
    C++,C#,Java,F# benutzen alle nominal subtyping, d.h. man muss Vererbung explizit angeben.
    In OCaml gibt es structural subtyping, d.h. man muss nicht explizit Vererbungen angeben; es reicht, wenn eine Klasse von den Typen und Namen her die gleichen Methoden implementiert wie eine andere.
    Templates in C++ erlauben auch, soweit ich weiß, Klassen so zu benutzen, als würden sie ein "Interface" implementieren, auch wenn das nicht explizit angegeben wurde. Templates erlauben also eine Art structural subtyping.

    Das Beispiel in OCaml:

    class a =
    object
      method f (i : int) =
        print_endline ("A sagt: " ^ string_of_int i)
    end
    
    class virtual b =
    object
      method virtual f : int -> unit
    end
    
    class c =
    object
      method g (x : b) =
        x#f 42
    end
    
    let c0 = new c ;;
    c0#g (new a)
    


  • alf42red schrieb:

    Templates in C++ erlauben auch, soweit ich weiß, Klassen so zu benutzen, als würden sie ein "Interface" implementieren, auch wenn das nicht explizit angegeben wurde.

    Schon, allerdings werden nur die Dinge gefordert, die in dem Template auch genutzt werden.



  • cooky451 schrieb:

    alf42red schrieb:

    Templates in C++ erlauben auch, soweit ich weiß, Klassen so zu benutzen, als würden sie ein "Interface" implementieren, auch wenn das nicht explizit angegeben wurde.

    Schon, allerdings werden nur die Dinge gefordert, die in dem Template auch genutzt werden.

    Richtig, genau das gehört auch zu structural subtyping. Im Beispiel kann man die Klasse b weglassen, und c bekommt einen Typ-Parameter.
    Geht auch in OCaml:

    class ['a] c =
    object
      method g (x : 'a) : unit =
        x#f 42
    end
    

    Die Benutzung von f erzeugt dann eine Bedingung an den Typ-Parameter.



  • Templates ermöglichen keinen dynamic dispatch, daher sind sie kein vollständiger Ersatz für so ein Typsystem. Weiß jemand, warum man sich bei c++ für ein nominative type system entschieden hat? Ich sehe keine Vorteile im Gegensatz zum OCaml Ansatz.

    cooky451 schrieb:

    Woher weiß B, welche Funktion aufgerufen werden muss?

    Man könnte im vtable ReturnType (*)( void *obj_mem /*, restliche Parameter*/ ) Funktionspointer speichern.



  • F# Spec schrieb:

    5.4.6 Type Equivalence
    Two static types ty1 and ty2 are definitely equivalent (with respect to a set of current inference constraints) if:
     ty1 has form op<ty11, ..., ty1n>, ty2 has form op<ty21, ..., ty2n> and each ty1i is definitely equivalent to ty2i for all 1 <= i <= n; or
     ty1 and ty2 are both variable types, and they both refer to the same binding site or are the same type inference variable.

    F# (ebenso Scala) hat auch ein strukturelles Typesystem, wie man ließt.



  • Zeus schrieb:

    F# Spec schrieb:

    5.4.6 Type Equivalence
    Two static types ty1 and ty2 are definitely equivalent (with respect to a set of current inference constraints) if:
     ty1 has form op<ty11, ..., ty1n>, ty2 has form op<ty21, ..., ty2n> and each ty1i is definitely equivalent to ty2i for all 1 <= i <= n; or
     ty1 and ty2 are both variable types, and they both refer to the same binding site or are the same type inference variable.

    F# (ebenso Scala) hat auch ein strukturelles Typesystem, wie man ließt.

    Das sieht strukturell aus, aber die constraints sind nicht so mächtig.
    In 5.2.3 sieht man, wie die member constraints eingeschränkt werden, damit alles zur Compile-Zeit eliminiert werden kann. (z.B. wird inline in der Definition verlangt)
    Das ist plausibel, weil .NET eigentlich keine strukturellen Subtypen vorsieht.
    Deswegen geht folgendes nicht in F#, während es in OCaml (mit # statt . ) geht:

    let f x = x.g () ;;
    

    Im Gegensatz zu F# haben Scala und OCaml wirklich strukturelle Subtypen.



  • Ja rede an mir vorbei.

    alf42red schrieb:

    Zeus schrieb:

    ...
    F# (ebenso Scala) hat auch ein strukturelles Typesystem, wie man ließt.

    Das ist plausibel, weil .NET eigentlich keine strukturellen Subtypen vorsieht.

    Ouch Scala läuft auf der JVM, welche auch keine "strukturellen Subtypen" kennt, weil das Typesystem für den Bytecode auch nominal ist. Also ist das wohl kein Gegenargument, aber ein Gegenbeweis, weil Scala zur Zeit für .NET Portiert wird, und soweit ich weiß macht das Typesystem keine Probleme, sondern eher Generics.

    alf42red schrieb:

    Deswegen geht folgendes nicht in F#, während es in OCaml (mit # statt . ) geht:

    let f x = x.g () ;;
    

    Im Gegensatz zu F# haben Scala und OCaml wirklich strukturelle Subtypen.

    Dein Code wird in Scala auch nicht funktionieren, weil Scala's Typeinferenz beschränkt ist.


Anmelden zum Antworten