Quali lingue tipizzate staticamente supportano i tipi di intersezione per i valori di ritorno delle funzioni?

16

Initial note:

This question got closed after several edits because I lacked the proper terminology to state accurately what I was looking for. Sam Tobin-Hochstadt then posted a comment which made me recognise exactly what that was: programming languages that support intersection types for function return values.

Now that the question has been re-opened, I've decided to improve it by rewriting it in a (hopefully) more precise manner. Therefore, some answers and comments below might no longer make sense because they refer to previous edits. (Please see the question's edit history in such cases.)

Ci sono dei popolari staticamente e amp; linguaggi di programmazione strongmente tipizzati (come Haskell, Java generico, C #, F #, ecc.) che supportano l'intersezione tipi per i valori di ritorno delle funzioni? Se sì, quali e come?

(Se sono onesto, mi piacerebbe molto vedere qualcuno dimostrare un modo come esprimere i tipi di intersezione in un linguaggio mainstream come C # o Java.)

Darò un rapido esempio di come potrebbero essere i tipi di intersezioni, usando alcuni pseudocodici simili a C #:

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();
}

Cioè, la funzione fn restituisce un'istanza di un certo tipo T , di cui il chiamante sa solo che implementa le interfacce IX e IY . (Cioè, a differenza dei generici, il chiamante non può scegliere il tipo concreto di T - la funzione lo fa. Da questo suppongo che T non sia in effetti un tipo universale, ma un tipo esistenziale. )

P.S.: Sono consapevole che è possibile definire semplicemente interface IXY : IX, IY e modificare il tipo di ritorno di fn in IXY . Tuttavia, non è proprio la stessa cosa, perché spesso non è possibile imbucare un'interfaccia aggiuntiva IXY a un tipo precedentemente definito A che implementa solo IX e IY separatamente.

Footnote: Some resources about intersection types:

Wikipedia article for "Type system" has a subsection about intersection types.

Report by Benjamin C. Pierce (1991), "Programming With Intersection Types, Union Types, and Polymorphism"

David P. Cunningham (2005), "Intersection types in practice", which contains a case study about the Forsythe language, which is mentioned in the Wikipedia article.

A Stack Overflow question, "Union types and intersection types" which got several good answers, among them this one which gives a pseudocode example of intersection types similar to mine above.

    
posta stakx 24.02.2012 - 12:07
fonte

9 risposte

5

Scala ha i tipi di intersezione completi incorporati nella lingua:

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()
    
risposta data 02.11.2012 - 02:36
fonte
9

In realtà, la risposta ovvia è: Java

Mentre potrebbe sorprendervi sapere che Java supporta i tipi di intersezione ... lo fa attraverso "&" tipo di operatore associato. Ad esempio:

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

Vedi questo link su più tipi di limiti in Java e anche questo dall'API Java.

    
risposta data 18.10.2012 - 03:26
fonte
8

La domanda originale ha richiesto "tipo ambiguo". Per questo la risposta è stata:

Tipo ambiguo, ovviamente nessuno. Il chiamante deve sapere cosa otterranno, quindi non è possibile. Tutto il linguaggio può restituire sia il tipo di base, l'interfaccia (possibilmente generata automaticamente come nel tipo di intersezione) o il tipo dinamico (e il tipo dinamico è fondamentalmente solo tipo con chiamata per nome, ottieni e imposta metodi).

Interfaccia dedotta:

Quindi in pratica vuoi che restituisca un'interfaccia IXY che deriva IX e IY sebbene quell'interfaccia non sia stata dichiarata in A o B , probabilmente perché wasn dichiarato quando sono stati definiti questi tipi. In tal caso:

  • Tutto ciò che viene digitato in modo dinamico, ovviamente.
  • Non ricordo alcun linguaggio mainstream tipizzato staticamente sarebbe in grado di generare l'interfaccia (è il tipo union di A e B o il tipo di intersezione di IX e IY ) stesso.
  • GO , perché le sue classi implementano l'interfaccia se hanno i metodi corretti, senza mai dichiararli. Quindi dichiari solo un'interfaccia che ne deriva i due e la restituisce.
  • Ovviamente qualsiasi altra lingua in cui il tipo può essere definito per implementare l'interfaccia al di fuori della definizione di quel tipo, ma non credo di ricordare altro che GO.
  • Non è possibile in nessun tipo dove l'implementazione di un'interfaccia debba essere definita nella definizione del tipo stesso. Tuttavia, è possibile aggirare la maggior parte di essi definendo il wrapper che implementa le due interfacce e delega tutti i metodi a un oggetto spostato.

PS Un linguaggio tipografico strongmente è quello in cui un oggetto di un determinato tipo non può essere trattato come oggetto di un altro tipo, mentre debolmente la lingua dattiloscritta è quella che ha un cast reinterpretato. Pertanto tutti i linguaggi digitati dinamicamente sono tipizzati strongmente , mentre i linguaggi tipizzati debolmente sono assembly, C e C ++, tutti e tre digitati staticamente .

    
risposta data 24.02.2012 - 12:23
fonte
3

Il linguaggio di programmazione Go tipo di ha questo, ma solo per i tipi di interfaccia.

In Vai qualsiasi tipo per cui sono definiti i metodi corretti implementa automaticamente un'interfaccia, quindi l'obiezione nel tuo P.S. non si applica In altre parole, è sufficiente creare un'interfaccia che abbia tutte le operazioni dei tipi di interfaccia da combinare (per i quali esiste una sintassi semplice) e tutto funzioni correttamente.

Un esempio:

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
}
    
risposta data 25.02.2012 - 01:36
fonte
3

Potresti essere in grado di fare ciò che vuoi usando un tipo esistenziale limitato, che può essere codificato in qualsiasi linguaggio con generici e polimorfismo limitato, ad es. C #.

Il tipo restituito sarà qualcosa di simile (nel codice psuedo)

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

o 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);
    }
}

Nota: non l'ho provato.

Il punto è che IAB deve essere in grado di applicare un IABFunc per qualsiasi tipo di ritorno R , e un IABFunc deve essere in grado di lavorare su qualsiasi T che sottotipi sia IA che% % co_de.

L'intento di IB è solo quello di racchiudere un DefaultIAB esistente che sottotipi T e IA . Tieni presente che questo è diverso dal tuo IB in quanto IAB : IA, IB può sempre essere aggiunto a un DefaultIAB esistente in seguito.

References:

risposta data 01.11.2012 - 23:52
fonte
3

TypeScript è un'altra lingua tipizzata che supporta i tipi di intersezione T & U (insieme ai tipi di unione T | U ). Ecco un esempio citato dalla relativa pagina della documentazione sui tipi avanzati :

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;
}
    
risposta data 10.06.2016 - 10:07
fonte
2

Ceylon ha pieno supporto per l'unione di prima classe e tipi di intersezione .

scrivi un tipo di unione come X | Y e un tipo di intersezione come X & Y .

Ancora meglio, Ceylon offre molti ragionamenti sofisticati su questi tipi, tra cui:

  • istanze principali: per esempio, Consumer<X>&Consumer<Y> è dello stesso tipo di Consumer<X|Y> se Consumer è controvariante in X , e
  • disgiunzione: per esempio, Object&Null è dello stesso tipo di Nothing , il tipo in basso.
risposta data 29.07.2015 - 22:53
fonte
0

Le funzioni C ++ hanno tutte un tipo di ritorno fisso, ma se restituiscono puntatori i puntatori possono, con restrizioni, puntare a tipi diversi.

Esempio:

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;
}

Il comportamento del puntatore restituito dipende dalle funzioni virtual definite e puoi eseguire un downcast controllato con, ad esempio,

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

Ecco come il polimorfismo funziona in C ++.

    
risposta data 24.02.2012 - 17:21
fonte
-1

Python

È scritto molto, molto strongmente.

Ma il tipo non viene dichiarato quando viene creata una funzione, quindi gli oggetti restituiti sono "ambigui".

Nella tua domanda specifica, un termine migliore potrebbe essere "Polimorfico". Questo è il caso d'uso comune in Python è quello di restituire tipi di varianti che implementano un'interfaccia comune.

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

Poiché Python è strongmente digitato, l'oggetto risultante sarà un'istanza di This o That e non può (facilmente) essere forzato o lanciato su un altro tipo di oggetto.

    
risposta data 24.02.2012 - 13:36
fonte

Leggi altre domande sui tag