Bahasa yang diketik secara statis mana yang mendukung tipe persimpangan untuk nilai pengembalian fungsi?

17

Catatan awal:

Pertanyaan ini ditutup setelah beberapa pengeditan karena saya tidak memiliki terminologi yang tepat untuk menyatakan secara akurat apa yang saya cari. Sam Tobin-Hochstadt kemudian memposting komentar yang membuat saya mengenali persis apa itu: bahasa pemrograman yang mendukung tipe persimpangan untuk nilai-nilai fungsi kembali.

Sekarang pertanyaan telah dibuka kembali, saya telah memutuskan untuk memperbaikinya dengan menulis ulang dengan (mudah-mudahan) cara yang lebih tepat. Karenanya, beberapa jawaban dan komentar di bawah mungkin tidak lagi masuk akal karena merujuk pada suntingan sebelumnya. (Silakan lihat riwayat edit pertanyaan dalam kasus semacam itu.)

Apakah ada bahasa pemrograman yang populer secara statis & sangat diketik (seperti Haskell, Java generik, C #, F #, dll.) Yang mendukung tipe persimpangan untuk nilai pengembalian fungsi? Jika ya, yang mana, dan bagaimana?

(Jika saya jujur, saya akan sangat senang melihat seseorang menunjukkan cara bagaimana mengekspresikan jenis persimpangan dalam bahasa umum seperti C # atau Java.)

Saya akan memberikan contoh cepat tentang apa jenis persimpangan mungkin terlihat, menggunakan beberapa pseudocode mirip dengan 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();
}

Yaitu, fungsi fnmengembalikan sebuah instance dari beberapa tipe T, di mana si penelepon hanya tahu bahwa ia mengimplementasikan antarmuka IXdan IY. (Yaitu, tidak seperti dengan obat generik, penelepon tidak dapat memilih tipe konkret T- fungsinya. Dari sini saya kira itu Tsebenarnya bukan tipe universal, tetapi tipe eksistensial.)

PS: Saya sadar bahwa seseorang dapat dengan mudah mendefinisikan interface IXY : IX, IYdan mengubah tipe pengembalian fnke IXY. Namun, itu bukan hal yang benar-benar sama, karena sering kali Anda tidak dapat menghubungkan antarmuka tambahan IXYke tipe Ayang sebelumnya didefinisikan yang hanya mengimplementasikan IXdan IYsecara terpisah.


Catatan kaki: Beberapa sumber tentang tipe persimpangan:

Artikel Wikipedia untuk "Tipe sistem" memiliki subbagian tentang jenis persimpangan .

Laporan oleh Benjamin C. Pierce (1991), "Pemrograman Dengan Jenis Persimpangan, Jenis Serikat, dan Polimorfisme"

David P. Cunningham (2005), "Jenis titik-temu dalam praktik" , yang berisi studi kasus tentang bahasa Forsythe, yang disebutkan dalam artikel Wikipedia.

Pertanyaan A Stack Overflow, "jenis Union dan jenis persimpangan" yang mendapat beberapa jawaban yang baik, di antaranya satu ini yang memberikan contoh pseudocode dari jenis persimpangan sama dengan saya di atas.

stakx
sumber
6
Bagaimana ini ambigu? Tmendefinisikan tipe, bahkan jika itu hanya didefinisikan dalam deklarasi fungsi sebagai "beberapa tipe yang meluas / mengimplementasikan IXdan IY". Fakta bahwa nilai pengembalian aktual adalah kasus khusus yang ( Aatau Bmasing - masing) bukanlah sesuatu yang istimewa di sini, Anda bisa mencapainya dengan menggunakan Objectalih-alih T.
Joachim Sauer
1
Ruby memungkinkan Anda mengembalikan apa pun yang Anda inginkan dari suatu fungsi. Sama untuk bahasa dinamis lainnya.
thorsten müller
Saya telah memperbarui jawaban saya. @ Joachim: Saya sadar bahwa istilah "ambigous" tidak menangkap konsep yang dipertanyakan dengan sangat akurat, jadi contoh untuk memperjelas makna yang dimaksud.
stakx
1
Iklan PS: ... yang mengubah pertanyaan Anda menjadi "bahasa mana yang memungkinkan jenis perlakuan Tsebagai antarmuka Isaat mengimplementasikan semua metode antarmuka, tetapi tidak menyatakan antarmuka itu".
Jan Hudec
6
Adalah kesalahan untuk menutup pertanyaan ini, karena ada jawaban yang tepat, yaitu tipe serikat pekerja . Jenis penyatuan tersedia dalam bahasa seperti (Raket yang Diketik) [ docs.racket-lang.org/ts-guide/] .
Sam Tobin-Hochstadt

Jawaban:

5

Scala memiliki tipe persimpangan penuh yang dibangun ke dalam bahasa:

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()
Api Ptharien
sumber
scala berbasis dotty akan memiliki tipe persimpangan yang benar, tetapi tidak untuk scala saat ini / sebelumnya.
Hongxu Chen
9

Sebenarnya, jawaban yang jelas adalah: Jawa

Meskipun mungkin mengejutkan Anda mengetahui bahwa Java mendukung tipe persimpangan ... itu memang melalui operator terikat tipe "&". Sebagai contoh:

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

Lihat tautan ini pada beberapa batasan tipe di Java, dan juga ini dari Java API.

redjamjar
sumber
Apakah itu akan berhasil jika Anda tidak tahu jenisnya pada waktu kompilasi? Yaitu satu dapat menulis <T extends IX & IY> T f() { if(condition) return new A(); else return new B(); }. Dan bagaimana Anda memanggil fungsi dalam kasus seperti itu? A atau B tidak dapat muncul di situs panggilan, karena Anda tidak tahu mana yang akan Anda dapatkan.
Jan Hudec
Ya, Anda benar --- ini tidak setara dengan contoh asli yang diberikan karena Anda perlu memberikan jenis yang konkret. Jika kita bisa menggunakan wildcard dengan batas persimpangan, maka kita akan memilikinya. Sepertinya kita tidak bisa ... dan saya tidak tahu kenapa tidak (lihat ini ). Tapi, tetap saja Java memang memiliki jenis persimpangan semacam ...
redjamjar
1
Saya merasa kecewa bahwa saya entah bagaimana tidak pernah belajar tentang jenis persimpangan dalam 10 tahun saya melakukan Jawa. Sekarang saya menggunakan semua waktu dengan Flowtype saya pikir mereka adalah fitur hilang terbesar di Jawa, hanya untuk menemukan saya belum pernah sekalipun melihatnya di alam liar. Orang-orang sangat kurang memanfaatkan mereka. Saya pikir jika mereka lebih dikenal, maka kerangka kerja injeksi ketergantungan seperti Spring tidak akan pernah menjadi begitu populer.
Andy
8

Pertanyaan awal meminta "tipe ambigu". Untuk itu jawabannya adalah:

Tipe ambigu, jelas tidak ada. Penelepon perlu tahu apa yang akan mereka dapatkan, jadi itu tidak mungkin. Semua bahasa apa pun dapat kembali adalah tipe dasar, antarmuka (mungkin dihasilkan secara otomatis seperti pada tipe persimpangan) atau tipe dinamis (dan tipe dinamis pada dasarnya hanya ketik dengan panggilan nama, dapatkan, dan atur metode).

Antarmuka yang disimpulkan:

Jadi pada dasarnya Anda ingin mengembalikan antarmuka IXYyang berasal IX dan IY meskipun antarmuka itu tidak dideklarasikan di salah satu Aatau B, mungkin karena tidak dideklarasikan ketika tipe tersebut didefinisikan. Dalam hal itu:

  • Apa pun yang diketik secara dinamis, jelas.
  • Saya tidak ingat bahasa mainstream yang diketik secara statis akan dapat menghasilkan antarmuka (itu adalah jenis persatuanA dan Batau tipe persimpangan IXdan IY) itu sendiri.
  • GO , karena itu kelas mengimplementasikan antarmuka jika mereka memiliki metode yang benar, tanpa pernah mendeklarasikannya. Jadi, Anda hanya mendeklarasikan antarmuka yang mendapatkan keduanya di sana dan mengembalikannya.
  • Tentunya bahasa lain di mana tipe dapat didefinisikan untuk mengimplementasikan antarmuka di luar definisi jenis itu, tapi saya rasa saya tidak ingat selain GO.
  • Tidak mungkin dalam tipe apa pun di mana mengimplementasikan antarmuka harus didefinisikan dalam definisi tipe itu sendiri. Namun Anda dapat bekerja di sebagian besar dari mereka dengan mendefinisikan wrapper yang mengimplementasikan dua antarmuka dan mendelegasikan semua metode ke objek yang dibungkus.

PS A sangat bahasa diketik adalah satu di mana sebuah objek dari tipe yang diberikan tidak dapat diperlakukan sebagai objek jenis lain, sementara lemah bahasa diketik adalah salah satu yang memiliki pemain menafsirkan. Jadi semua bahasa yang diketik secara dinamis diketik dengan kuat , sementara bahasa yang diketik dengan lemah adalah assembly, C dan C ++, ketiganya diketik secara statis .

Jan Hudec
sumber
Ini tidak benar tidak ada yang ambigu tentang jenisnya. Suatu bahasa dapat mengembalikan apa yang disebut "jenis persimpangan" --- hanya sedikit jika ada bahasa umum yang melakukannya.
redjamjar
@redjamjar: Pertanyaannya memiliki kata-kata yang berbeda ketika saya menjawabnya dan meminta "tipe ambigu". Itu sebabnya itu dimulai dengan itu. Pertanyaan itu secara signifikan ditulis ulang sejak itu. Saya akan memperluas jawabannya dengan menyebutkan yang asli dan yang sekarang.
Jan Hudec
maaf, saya sangat merindukan itu!
redjamjar
+1 untuk menyebutkan Golang, yang mungkin merupakan contoh terbaik dari bahasa umum yang memungkinkan ini, bahkan jika cara melakukannya sedikit memutar.
Jules
3

Go Bahasa Pemrograman jenis memiliki ini, tapi hanya untuk jenis antarmuka.

Di Go, tipe apa pun yang metode yang benar didefinisikan secara otomatis mengimplementasikan antarmuka, sehingga keberatan dalam PS Anda tidak berlaku. Dengan kata lain, buat saja sebuah antarmuka yang memiliki semua operasi dari jenis antarmuka yang akan digabungkan (yang ada sintaksinya sederhana) dan semuanya hanya berfungsi.

Sebuah contoh:

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
}
Frits
sumber
3

Anda mungkin dapat melakukan apa yang Anda inginkan dengan menggunakan tipe eksistensial terbatas, yang dapat dikodekan dalam bahasa apa pun dengan generik dan polimorfisme terikat, misalnya C #.

Jenis pengembalian akan menjadi seperti (dalam kode psuedo)

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

atau dalam 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);
    }
}

Catatan: Saya belum menguji ini.

Intinya adalah bahwa IABharus dapat menerapkan IABFunc untuk semua jenis pengembalian R, danIABFunc harus mampu bekerja pada Tsubtipe mana saja IAdan IB.

Maksudnya DefaultIABadalah hanya untuk membungkus yang ada Tsubtipe IAdanIB . Perhatikan bahwa ini berbeda dari IAB : IA, IBdi Anda yang DefaultIABselalu dapat ditambahkan ke yang sudah ada di Tkemudian hari.

Referensi:

Phil Freeman
sumber
Pendekatan ini berfungsi jika seseorang menambahkan tipe pembungkus objek generik dengan parameter T, IA, IB, dengan T dibatasi ke antarmuka, yang merangkum referensi tipe T dan memungkinkan Applyuntuk dipanggil padanya. Masalah besar adalah bahwa tidak ada cara untuk menggunakan fungsi anonim untuk mengimplementasikan antarmuka, jadi konstruksi seperti itu akhirnya menjadi rasa sakit yang nyata untuk digunakan.
supercat
3

TypeScript adalah bahasa yang diketik lain yang mendukung jenis persimpangan T & U(bersama dengan jenis serikat T | U). Berikut adalah contoh yang dikutip dari halaman dokumentasi mereka tentang tipe lanjutan :

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;
}
stakx
sumber
2

Ceylon memiliki dukungan penuh untuk jenis persatuan dan persimpangan kelas satu .

Anda menulis tipe gabungan sebagai X | Ydan tipe persimpangan sebagai X & Y.

Bahkan lebih baik, Ceylon menghadirkan banyak alasan canggih tentang jenis-jenis ini, termasuk:

  • Instansiasi utama: misalnya, Consumer<X>&Consumer<Y>adalah tipe yang sama seperti Consumer<X|Y>jika Consumercontravarian di X, dan
  • disjointness: misalnya, Object&Nulladalah tipe yang sama dengan Nothing, tipe bawah.
Gavin King
sumber
0

Fungsi-fungsi C ++ semuanya memiliki tipe pengembalian tetap, tetapi jika mereka mengembalikan pointer, pointer dapat, dengan batasan, menunjuk ke tipe yang berbeda.

Contoh:

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

Perilaku pointer yang dikembalikan akan tergantung pada virtualfungsi apa yang didefinisikan, dan Anda dapat melakukan pengecekan downcast dengan, katakanlah,

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

Begitulah cara polimorfisme bekerja di C ++.

David Thornley
sumber
Dan tentu saja orang dapat menggunakan suatu anyatau variantjenis, seperti yang disediakan template boost. Dengan demikian, batasannya tidak tinggal.
Deduplicator
Namun, ini bukan pertanyaan yang diajukan, yang merupakan cara untuk menentukan bahwa tipe pengembalian meluas dua superclasses yang teridentifikasi pada saat yang sama, yaitu class Base1{}; class Base2{}; class Derived1 : public Base1, public Base2 {}; class Derived2 : public Base1, public Base2 {}... sekarang jenis apa yang dapat kita tentukan yang memungkinkan untuk kembali Derived1atau Derived2tidak Base1juga tidak Base2langsung?
Jules
-1

Python

Ini sangat, sangat sangat diketik.

Tetapi tipe ini tidak dideklarasikan ketika sebuah fungsi dibuat, jadi objek yang dikembalikan "ambigu".

Dalam pertanyaan spesifik Anda, istilah yang lebih baik mungkin "Polimorfik". Itu kasus penggunaan umum dalam Python adalah untuk mengembalikan jenis varian yang mengimplementasikan antarmuka umum.

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

Karena Python diketik dengan kuat, objek yang dihasilkan akan menjadi turunan dari Thisdan Thatdan tidak dapat (dengan mudah) dipaksa atau dilemparkan ke jenis objek lain.

S.Lott
sumber
Ini cukup menyesatkan; sementara tipe suatu objek tidak dapat diubah, nilai-nilai dapat dikonversi antar tipe dengan cukup mudah. Untuk str, misalnya, sepele.
James Youngman
1
@JamesYoungman: Apa? Itu berlaku untuk semua bahasa. Semua bahasa yang pernah saya lihat memiliki to_string konversi kiri, kanan dan tengah. Saya tidak mendapatkan komentar Anda sama sekali. Bisakah Anda menguraikan?
S.Lott
Saya mencoba memahami apa yang Anda maksud dengan "Python sangat diketik". Mungkin saya salah mengerti apa yang Anda maksud dengan "sangat diketik". Frankly Python memiliki beberapa karakteristik yang akan saya kaitkan dengan bahasa yang sangat diketik. Sebagai contoh, ia menerima program-program di mana jenis kembali fungsi tidak kompatibel dengan penggunaan nilai pemanggil. Misalnya, "x, y = F (z)" di mana F () mengembalikan (z, z, z).
James Youngman
Jenis objek Python tidak dapat (tanpa sihir serius) dapat diubah. Tidak ada operator "pemeran" seperti halnya dengan Java dan C ++. Itu membuat setiap objek sangat diketik. Nama variabel dan nama fungsi tidak memiliki tipe pengikatan, tetapi objek itu sendiri sangat diketik. Konsep kunci di sini bukanlah keberadaan deklarasi. Konsep kuncinya adalah tersedianya operator cor. Perhatikan juga bahwa ini bagi saya faktual; moderator dapat membantah hal itu.
S.Lott
1
Operasi C dan C ++ cast tidak mengubah tipe operan mereka juga.
James Youngman