Welche statisch typisierten Sprachen unterstützen Schnittpunkttypen für Funktionsrückgabewerte?


17

Anfangshinweis:

Diese Frage wurde nach mehreren Änderungen geschlossen, da mir die richtige Terminologie fehlte, um genau zu sagen, wonach ich suchte. Sam Tobin-Hochstadt hat dann einen Kommentar gepostet, der mich genau erkennen ließ, was das war: Programmiersprachen, die Schnittpunkttypen für Funktionsrückgabewerte unterstützen.

Nachdem die Frage erneut geöffnet wurde, habe ich beschlossen, sie zu verbessern, indem ich sie (hoffentlich) genauer umschreibe. Aus diesem Grund sind einige der folgenden Antworten und Kommentare möglicherweise nicht mehr sinnvoll, da sie sich auf frühere Änderungen beziehen. (Bitte lesen Sie in solchen Fällen den Bearbeitungsverlauf der Frage.)

Gibt es beliebte statisch und stark typisierte Programmiersprachen (wie Haskell, generisches Java, C #, F # usw.), die Schnittpunkttypen für Funktionsrückgabewerte unterstützen? Wenn ja, welche und wie?

(Wenn ich ehrlich bin, würde ich wirklich gerne jemanden sehen, der demonstriert, wie Schnittstellentypen in einer gängigen Sprache wie C # oder Java ausgedrückt werden können.)

Ich werde ein kurzes Beispiel dafür geben, wie Schnittpunkttypen mit einem Pseudocode ähnlich C # aussehen könnten:

interface IX { … }
interface IY { … }
interface IB { … }

class A : IX, IY { … }
class B : IX, IY, IB { … }

T fn()  where T : IX, IY
{
    return … ? new A()  
             : new B();
}

Das heißt, die Funktion fngibt eine Instanz eines Typs zurück T, von der der Aufrufer nur weiß, dass sie Interfaces IXund implementiert IY. (Das heißt, im Gegensatz zu Generika kann der Aufrufer nicht den konkreten Typ auswählen , sondern Tdie Funktion. Ich würde davon ausgehen, dass Tes sich in der Tat nicht um einen universellen Typ, sondern um einen existenziellen Typ handelt.)

PS: Mir ist bewusst, dass man einfach eine definieren interface IXY : IX, IYund den Rückgabetyp von fnauf ändern könnte IXY. Dies ist jedoch nicht das gleiche, da Sie eine zusätzliche Schnittstelle häufig nicht IXYan einen zuvor definierten Typ anschrauben können, Ader nur implementiert IXund IYseparat ist.


Fußnote: Einige Ressourcen zu Schnittpunkttypen:

Wikipedia-Artikel für "Typensystem" enthält einen Unterabschnitt über Schnittpunkttypen .

Bericht von Benjamin C. Pierce (1991), "Programmieren mit Schnitttypen, Unionstypen und Polymorphismus"

David P. Cunningham (2005), "Intersection types in practice" , der eine Fallstudie über die Forsythe-Sprache enthält, die im Wikipedia-Artikel erwähnt wird.

Eine Stapelüberlauf-Frage, "Verbindungstypen und Kreuzungstypen", die mehrere gute Antworten erhielt, darunter diese, die ein Pseudocode-Beispiel für Kreuzungstypen ähnlich der oben genannten gibt.


6
Wie ist das zweideutig? Tdefiniert einen Typ, auch wenn er nur in der Funktionsdeklaration als "ein Typ, der erweitert / implementiert IXund IY" definiert ist. Die Tatsache, dass der tatsächliche Rückgabewert ein Sonderfall davon ist ( Abzw. ist B), ist hier nichts Besonderes. Sie können dies auch erreichen, indem Sie Objectanstelle von verwenden T.
Joachim Sauer

1
Mit Ruby können Sie von einer Funktion alles zurückgeben, was Sie wollen. Gleiches gilt für andere dynamische Sprachen.
thorsten müller

Ich habe meine Antwort aktualisiert. @Joachim: Mir ist bewusst, dass der Begriff "ambivalent" den fraglichen Begriff nicht sehr genau erfasst, also das Beispiel zur Verdeutlichung der beabsichtigten Bedeutung.
Stakx

1
Anzeige PS: ... ändert Ihre Frage in "In welcher Sprache kann der Typ Tals Schnittstelle behandelt werden, Iwenn alle Methoden der Schnittstelle implementiert werden, diese Schnittstelle wurde jedoch nicht deklariert".
Jan Hudec

6
Es war ein Fehler, diese Frage zu schließen, weil es eine genaue Antwort gibt, nämlich Gewerkschaftstypen . Union-Typen sind in Sprachen wie (Typed Racket) [ docs.racket-lang.org/ts-guide/] verfügbar .
Sam Tobin-Hochstadt

Antworten:


5

In Scala sind vollständige Schnittpunkttypen in die Sprache integriert:

trait IX {...}
trait IY {...}
trait IB {...}

class A() extends IX with IY {...}

class B() extends IX with IY with IB {...}

def fn(): IX with IY = if (...) new A() else new B()

dotty-basierte Scala hätte echte Kreuzungstypen, aber nicht für aktuelle / frühere Scala.
Hongxu Chen

9

Tatsächlich lautet die offensichtliche Antwort: Java

Es mag Sie überraschen, zu erfahren, dass Java Kreuzungstypen unterstützt. Dies geschieht in der Tat über den Operator "&". Beispielsweise:

<T extends IX & IY> T f() { ... }

Zeigen Sie diesen Link an mehreren Typgrenzen in Java an, und dies auch über die Java-API.


Funktioniert das, wenn Sie den Typ zur Kompilierungszeit nicht kennen? Dh kann man schreiben <T extends IX & IY> T f() { if(condition) return new A(); else return new B(); }. Und wie ruft man die Funktion in einem solchen Fall auf? Weder A noch B können am Anrufstandort angezeigt werden, da Sie nicht wissen, welchen Sie erhalten.
Jan Hudec

Ja, Sie haben Recht - es entspricht nicht dem ursprünglichen Beispiel, da Sie einen konkreten Typ angeben müssen. Wenn wir Platzhalter mit Überschneidungsgrenzen verwenden könnten, hätten wir es. Scheint, als könnten wir nicht ... und ich weiß nicht wirklich, warum nicht (siehe dies ). Aber Java hat immer noch Kreuzungstypen einer Art ...
Redjamjar

1
Ich bin enttäuscht, dass ich in den 10 Jahren, in denen ich Java gemacht habe, nie etwas über Kreuzungstypen gelernt habe. Jetzt, da ich Flowtype die ganze Zeit nutze, dachte ich, dass dies das größte fehlende Feature in Java ist, nur um festzustellen, dass ich sie noch nie in freier Wildbahn gesehen hatte. Die Leute nutzen sie ernsthaft zu wenig. Ich denke, wenn sie besser bekannt wären, dann wären Abhängigkeitsinjektions-Frameworks wie Spring noch nie so populär geworden.
Andy

8

Ursprüngliche Frage nach "mehrdeutigem Typ" gestellt. Dafür lautete die Antwort:

Mehrdeutiger Typ, offensichtlich keiner. Der Anrufer muss wissen, was er bekommt, also ist das nicht möglich. Alle Sprachen, die zurückgegeben werden können, sind entweder Basistyp, Schnittstelle (möglicherweise automatisch generiert wie im Schnittpunkttyp) oder dynamischer Typ (und dynamischer Typ ist im Grunde nur Typ mit Methoden zum Aufrufen, Abrufen und Festlegen von Namen).

Abgeleitete Schnittstelle:

Sie möchten also im Grunde genommen, dass eine Schnittstelle zurückgegeben wird IXY, die abgeleitet wird, IX und IY obwohl diese Schnittstelle entweder in Aoder nicht deklariert wurde B, möglicherweise, weil sie bei der Definition dieser Typen nicht deklariert wurde. In diesem Fall:

  • Jeder, der dynamisch getippt ist, offensichtlich.
  • Ich erinnere mich nicht, dass irgendeine statisch typisierte Mainstream-Sprache in der Lage wäre, die Schnittstelle selbst zu generieren (es ist der Union- Typ von Aund Boder der Schnittpunkt-Typ von IXund IY).
  • GO , weil es Klassen sind, die eine Schnittstelle implementieren, wenn sie die richtigen Methoden haben, ohne sie jemals zu deklarieren. Sie deklarieren also einfach eine Schnittstelle, die die beiden dort ableitet, und geben sie zurück.
  • Offensichtlich kann jede andere Sprache, in der ein Typ definiert werden kann, eine Schnittstelle außerhalb der Definition dieses Typs implementieren, aber ich glaube nicht, dass ich mich an etwas anderes als GO erinnere.
  • Es ist in keinem Typ möglich, in dem die Implementierung einer Schnittstelle in der Typdefinition selbst definiert werden muss. Sie können die meisten jedoch umgehen, indem Sie einen Wrapper definieren, der die beiden Schnittstellen implementiert und alle Methoden an ein umschlossenes Objekt delegiert.

PS Eine stark typisierte Sprache ist eine Sprache, in der ein Objekt eines bestimmten Typs nicht als Objekt eines anderen Typs behandelt werden kann, während eine schwach typisierte Sprache eine neu interpretierte Sprache ist. Somit sind alle dynamisch typisierten Sprachen stark typisiert , während schwach typisierte Sprachen Assembler, C und C ++ sind, wobei alle drei statisch typisiert sind .


Dies ist nicht korrekt, da der Typ nicht eindeutig ist. Eine Sprache kann sogenannte "Schnittmengen" zurückgeben - es sind nur so wenige, wenn es Mainstream-Sprachen gibt.
Redjamjar

@redjamjar: Die Frage hatte einen anderen Wortlaut, als ich sie beantwortete und nach "mehrdeutigem Typ" fragte. Deshalb fängt es damit an. Die Frage wurde seitdem deutlich umgeschrieben. Ich werde die Antwort erweitern, um sowohl den ursprünglichen als auch den aktuellen Wortlaut zu erwähnen.
Jan Hudec

Entschuldigung, das habe ich offensichtlich verpasst!
Redjamjar

+1 für die Erwähnung von Golang, das wahrscheinlich das beste Beispiel für eine gemeinsame Sprache ist, die dies ermöglicht, auch wenn die Art und Weise, dies zu tun, etwas umständlich ist.
Jules

3

Die Art von Go Programming Language hat dies, aber nur für Schnittstellentypen.

In Go implementiert jeder Typ, für den die richtigen Methoden definiert sind, automatisch eine Schnittstelle, sodass der Einwand in Ihrem PS nicht zutrifft. Mit anderen Worten, erstellen Sie einfach eine Schnittstelle, die alle Operationen der zu kombinierenden Schnittstellentypen enthält (für die es eine einfache Syntax gibt), und alles funktioniert.

Ein Beispiel:

package intersection

type (
    // The first component type.
    A interface {
        foo() int
    }
    // The second component type.
    B interface {
        bar()
    }

    // The intersection type.
    Intersection interface {
        A
        B
    }
)

// Function accepting an intersection type
func frob(x Intersection) {
    // You can directly call methods defined by A or B on Intersection.
    x.foo()
    x.bar()

    // Conversions work too.
    var a A = x
    var b B = x
    a.foo()
    b.bar()
}

// Syntax for a function returning an intersection type:
// (using an inline type definition to be closer to your suggested syntax)
func frob2() interface { A; B } {
    // return something
}

3

Möglicherweise können Sie tun, was Sie möchten, indem Sie einen begrenzten existenziellen Typ verwenden, der in einer beliebigen Sprache mit Generika und begrenztem Polymorphismus codiert werden kann, z. B. C #.

Der Rückgabetyp ist ungefähr (im Pseudocode)

IAB = exists T. T where T : IA, IB

oder in C #:

interface IAB<IA, IB>
{
    R Apply<R>(IABFunc<R, IA, IB> f);
}

interface IABFunc<R, IA, IB>
{
    R Apply<T>(T t) where T : IA, IB;
}

class DefaultIAB<T, IA, IB> : IAB<IA, IB> where T : IA, IB 
{
    readonly T t;

    ...

    public R Apply<R>(IABFunc<R, IA, IB> f) {
        return f.Apply<T>(t);
    }
}

Hinweis: Ich habe das nicht getestet.

Der Punkt ist , dass IABin die Lage sein hat eine IABFunc für jeden Rückgabetyp anzuwenden R, und eine IABFuncmuss Arbeit auf jedem in der Lage sein , Tdie beide Subtypen IAund IB.

Die Absicht von DefaultIABist es nur, einen existierenden TSubtypen IAund zu verpacken IB. Beachten Sie, dass dies anders ist als das IAB : IA, IB, DefaultIABwas später immer zu einem bestehenden hinzugefügt werden kann T.

Verweise:


Der Ansatz funktioniert, wenn man einen generischen Objekt-Wrapper-Typ mit den Parametern T, IA, IB hinzufügt, wobei T auf die Schnittstellen beschränkt ist, der eine Referenz vom Typ T einkapselt und es ermöglicht, Applyauf sie aufgerufen zu werden. Das große Problem ist, dass es keine Möglichkeit gibt, eine anonyme Funktion zum Implementieren einer Schnittstelle zu verwenden. Daher ist die Verwendung von Konstrukten dieser Art ein echtes Problem.
Supercat

3

TypeScript ist eine weitere typisierte Sprache, die Schnittpunkttypen T & U(zusammen mit Vereinigungstypen T | U) unterstützt. Hier ist ein Beispiel aus der Dokumentation zu erweiterten Typen :

function extend<T, U>(first: T, second: U): T & U {
    let result = <T & U>{};
    for (let id in first) {
        (<any>result)[id] = (<any>first)[id];
    }
    for (let id in second) {
        if (!result.hasOwnProperty(id)) {
            (<any>result)[id] = (<any>second)[id];
        }
    }
    return result;
}

2

Ceylon bietet volle Unterstützung für erstklassige Vereinigungs- und Kreuzungstypen .

Sie schreiben einen Vereinigungstyp als X | Yund einen Schnittpunkttyp als X & Y.

Noch besser ist, dass Ceylon viele raffinierte Überlegungen zu diesen Typen enthält, darunter:

  • Hauptinstanziierungen: Ist beispielsweise Consumer<X>&Consumer<Y>derselbe Typ, als Consumer<X|Y>ob er Consumerin Xund kontravariant wäre
  • Disjointness: Ist beispielsweise Object&Nullder gleiche Typ wie Nothingder unterste Typ.

0

C ++ - Funktionen haben alle einen festen Rückgabetyp, aber wenn sie Zeiger zurückgeben, können die Zeiger mit Einschränkungen auf verschiedene Typen verweisen.

Beispiel:

class Base {};
class Derived1: public Base {};
class Derived2: public Base{};

Base * function(int derived_type)
{
    if (derived_type == 1)
        return new Derived1;
    else
        return new Derived2;
}

Das Verhalten des zurückgegebenen Zeigers hängt davon ab, welche virtualFunktionen definiert sind, und Sie können beispielsweise mit

Base * foo = function(...);dynamic_cast<Derived1>(foo).

So funktioniert Polymorphismus in C ++.


Und natürlich kann man einen anyoder einen variantTyp verwenden, wie es der Templates Boost bietet. Somit bleibt die Einschränkung nicht bestehen.
Deduplizierer

Dies ist jedoch nicht ganz die Frage, die gestellt wurde, um anzugeben, dass der Rückgabetyp zwei identifizierte Superklassen gleichzeitig erweitert, dh class Base1{}; class Base2{}; class Derived1 : public Base1, public Base2 {}; class Derived2 : public Base1, public Base2 {}... nun, welchen Typ können wir angeben, der die Rückgabe von einem Derived1oder von Derived2keinem zulässt Base1noch Base2direkt?
Jules

-1

Python

Es ist sehr, sehr stark getippt.

Der Typ wird jedoch beim Erstellen einer Funktion nicht deklariert, sodass die zurückgegebenen Objekte "mehrdeutig" sind.

In Ihrer spezifischen Frage könnte ein besserer Begriff "polymorph" sein. Dies ist der gängige Anwendungsfall in Python, bei dem Variantentypen zurückgegeben werden, die eine gemeinsame Schnittstelle implementieren.

def some_function( selector, *args, **kw ):
    if selector == 'this':
        return This( *args, **kw )
    else:
        return That( *args, **kw )

Da Python stark typisiert ist, ist das resultierende Objekt eine Instanz von Thisoder Thatund kann nicht (leicht) zu einem anderen Objekttyp gezwungen oder umgewandelt werden.


Das ist ziemlich irreführend; Während der Typ eines Objekts so gut wie unveränderlich ist, können Werte leicht zwischen Typen konvertiert werden. Zum Beispiel trivial zu stricken.
James Youngman

1
@ JamesYoungman: Was? Das gilt für alle Sprachen. Alle Sprachen, die ich je gesehen habe, haben to_string-Konvertierungen links, rechts und zentriert. Ich verstehe Ihren Kommentar überhaupt nicht. Können Sie näher darauf eingehen?
S.Lott

Ich habe versucht zu verstehen, was Sie unter "Python ist stark typisiert" verstanden haben. Vielleicht habe ich falsch verstanden, was Sie unter "stark getippt" verstanden haben. Ehrlich gesagt hat Python nur wenige Eigenschaften, die ich mit stark typisierten Sprachen verbinden würde. Beispielsweise werden Programme akzeptiert, in denen der Rückgabetyp einer Funktion nicht mit der Verwendung des Werts durch den Aufrufer kompatibel ist. Zum Beispiel "x, y = F (z)", wobei F () (z, z, z) zurückgibt.
James Youngman

Der Typ eines Python-Objekts kann (ohne ernsthafte Magie) nicht geändert werden. Es gibt keinen "Cast" -Operator wie bei Java und C ++. Das macht jedes Objekt stark typisiert. Variablennamen und Funktionsnamen haben keine Typbindung, aber die Objekte selbst sind stark typisiert. Das Schlüsselkonzept ist hier nicht das Vorhandensein von Erklärungen. Das Schlüsselkonzept ist die Verfügbarkeit von Cast-Operatoren. Beachten Sie auch, dass mir dies sachlich erscheint; Moderatoren können dies jedoch bestreiten.
S.Lott

1
C- und C ++ - Umwandlungsoperationen ändern auch nicht den Typ ihres Operanden.
James Youngman
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.