Apakah ada use case untuk tipe bawah sebagai tipe parameter fungsi?

12

Jika suatu fungsi memiliki tipe pengembalian ⊥ ( tipe bawah ), itu artinya ia tidak pernah kembali. Misalnya dapat keluar atau melempar, keduanya situasi yang cukup biasa.

Agaknya jika suatu fungsi memiliki parameter tipe ⊥ itu tidak akan pernah bisa (dengan aman) dipanggil. Apakah ada alasan untuk mendefinisikan fungsi seperti itu?

bdsl
sumber

Jawaban:

17

Salah satu sifat mendefinisikan dari atau jenis kosong adalah bahwa ada fungsi A untuk setiap jenis A . Bahkan, ada fungsi yang unik . Oleh karena itu, cukup masuk akal untuk fungsi ini disediakan sebagai bagian dari perpustakaan standar. Seringkali disebut sesuatu seperti absurd. (Dalam sistem dengan subtyping, ini mungkin ditangani hanya dengan memiliki menjadi subtipe dari setiap jenis. Kemudian konversi implisit absurd. Pendekatan lain yang terkait adalah untuk menentukan sebagai α.α yang hanya dapat dipakai untuk semua jenis.)

Anda pasti ingin memiliki fungsi seperti itu atau yang sederajat karena itulah yang memungkinkan Anda memanfaatkan fungsi yang menghasilkan . Sebagai contoh, katakanlah saya diberi sejumlah tipe E+A . Saya melakukan analisis kasus di atasnya dan dalam kasus E saya akan melempar pengecualian menggunakan throw:E . Dalam A kasus, saya akan menggunakan f:AB . Secara keseluruhan, saya ingin nilai dari tipe B sehingga saya perlu melakukan sesuatu untuk mengubah menjadi B . Itu yang absurdakan saya lakukan.

Yang mengatakan, tidak ada seluruh banyak alasan untuk menentukan fungsi Anda sendiri A . Menurut definisi, mereka akan menjadi contoh absurd. Namun, Anda mungkin melakukannya jika absurdtidak disediakan oleh perpustakaan standar, atau Anda menginginkan jenis versi khusus untuk membantu pemeriksaan / inferensi tipe. Anda bisa, bagaimanapun, dengan mudah menghasilkan fungsi yang akan berakhir dipakai untuk tipe seperti A .

Meskipun tidak ada banyak alasan untuk menulis fungsi seperti itu, umumnya harus tetap diizinkan . Salah satu alasannya adalah karena menyederhanakan alat / makro pembuatan kode.

Derek Elkins meninggalkan SE
sumber
Jadi ini berarti sesuatu seperti (x ? 3 : throw new Exception())diganti untuk keperluan analisis dengan sesuatu yang lebih seperti (x ? 3 : absurd(throw new Exception()))?
bdsl
Jika Anda tidak memiliki subtipe atau belum mendefinisikan sebagai α . α , maka yang pertama tidak akan mengetik cek dan yang terakhir akan. Dengan subtyping, ya, sesuatu seperti secara implisit akan dimasukkan. Tentu saja, Anda bisa memasukkan definisi di dalamnya yang secara efektif apa yang mendefinisikan sebagai α . α akan dilakukan. α.αabsurdabsurdthrowα.α
Derek Elkins meninggalkan SE
6

Untuk menambah apa yang telah dikatakan tentang fungsi, absurd: ⊥ -> asaya memiliki contoh konkret tentang di mana fungsi ini sebenarnya berguna.

Pertimbangkan tipe data Haskell Free f ayang mewakili struktur pohon umum dengan fnode dan daun berbentuk yang mengandung as:

data Free f a = Op (f (Free f a)) | Var a

Pohon-pohon ini dapat dilipat dengan fungsi sebagai berikut:

fold :: Functor f => (a -> b) -> (f b -> b) -> Free f a -> b
fold gen alg (Var x) = gen x
fold gen alg (Op x) = alg (fmap (fold gen alg) x)

Secara singkat, operasi ini ditempatkan algpada node dan gendaun.

Sekarang ke intinya: semua struktur data rekursif dapat direpresentasikan menggunakan tipe data titik tetap. Dalam Haskell ini Fix fdan itu dapat didefinisikan sebagai type Fix f = Free f ⊥(yaitu Pohon dengan fnode -shaped dan tidak ada daun di luar functor f). Secara tradisional struktur ini memiliki lipatan juga, yang disebut cata:

cata :: Functor f => (f a -> a) -> Fix f -> a
cata alg x = fold absurd alg x

Yang memberikan penggunaan absurd yang cukup rapi: karena pohon tidak dapat memiliki daun (karena ⊥ tidak memiliki penghuni selain undefined), tidak mungkin menggunakan genuntuk lipatan ini dan absurdmenggambarkannya!

J_mie6
sumber
2

Tipe bawah adalah subtipe dari setiap tipe lainnya, yang bisa sangat berguna dalam praktiknya. Misalnya, tipe NULLdalam versi teoretis tipe-aman C harus berupa subtipe dari setiap tipe pointer lainnya, jika tidak Anda tidak dapat, mis., Kembali ke NULLtempat yang char*diharapkan; sama halnya, tipe undefineddalam JavaScript tipe-aman teoretis harus merupakan subtipe dari setiap tipe lain dalam bahasa.

exit()throw()IntIntexit()

TST

Draconis
sumber
3
NULLapakah tipe unit bukan, berbeda dari ⊥ yang merupakan tipe kosong?
bdsl
Saya tidak yakin apa artinya ≺ dalam teori tipe.
bdsl
1
@ bdsl Operator melengkung di sini adalah "adalah subtipe dari"; Saya tidak yakin apakah itu standar, itu hanya apa yang digunakan profesor saya.
Draconis
1
@ gnasher729 Benar, tetapi C juga tidak terlalu aman. Saya katakan jika Anda tidak bisa hanya melemparkan integer ke void*, Anda akan memerlukan jenis tertentu untuk itu yang dapat digunakan untuk semua jenis pointer.
Draconis
2

Ada satu kegunaan yang bisa saya pikirkan, dan itu adalah sesuatu yang telah dianggap sebagai peningkatan bahasa pemrograman Swift.

Swift memiliki maybeMonad, dieja Optional<T>atau T?. Ada banyak cara untuk berinteraksi dengannya.

  • Anda dapat menggunakan suka membuka bersyarat seperti

    if let nonOptional = someOptional {
        print(nonOptional)
    }
    else {
        print("someOptional was nil")
    }
    
  • Anda dapat menggunakan map, flatMapuntuk mengubah nilai

  • Force membuka bungkusan operator ( !, tipe (T?) -> T) untuk secara paksa membuka bungkusan konten, jika tidak memicu kerusakan
  • Operator nil-penggabungan ( ??, tipe (T?, T) -> T) untuk mengambil nilainya atau menggunakan nilai default:

    let someI = Optional(100)
    print(someI ?? 123) => 100 // "left operand is non-nil, unwrap it.
    
    let noneI: Int? = nil
    print(noneI ?? 123) // => 123 // left operand is nil, take right operand, acts like a "default" value
    

Sayangnya, tidak ada cara ringkas untuk mengatakan "membuka atau membuang kesalahan" atau "membuka atau crash dengan pesan kesalahan khusus". Sesuatu seperti

let someI: Int? = Optional(123)
let nonOptionalI: Int = someI ?? fatalError("Expected a non-nil value")

tidak mengkompilasi, karena fatalErrormemiliki tipe () -> Never( ()adalah Void, Swift' jenis unit, Neveradalah jenis bawah Swift). Memanggilnya menghasilkan Never, yang tidak kompatibel dengan yang Tdiharapkan sebagai operan yang tepat ??.

Dalam upaya untuk memperbaiki ini, Proposal Swift Evolution SE-0217- Operator "Unwrap or Die" diajukan. Itu pada akhirnya ditolak , tetapi itu membangkitkan minat dalam membuat Nevermenjadi subtipe dari semua jenis.

Jika Neverdibuat menjadi subtipe dari semua jenis, maka contoh sebelumnya akan dapat dikompilasi:

let someI: Int? = Optional(123)
let nonOptionalI: Int = someI ?? fatalError("Expected a non-nil value")

karena situs panggilan ??memiliki tipe (T?, Never) -> T, yang akan kompatibel dengan (T?, T) -> Ttanda tangan ??.

Alexander - Pasang kembali Monica
sumber
0

Swift memiliki tipe "Never" yang tampaknya sangat seperti tipe bawah: Sebuah fungsi yang dinyatakan untuk kembali Tidak pernah bisa kembali, fungsi dengan parameter tipe Never tidak pernah bisa dipanggil.

Ini berguna sehubungan dengan protokol, di mana mungkin ada pembatasan karena jenis sistem bahasa bahwa suatu kelas harus memiliki fungsi tertentu, tetapi tanpa persyaratan bahwa fungsi ini harus pernah dipanggil, dan tidak ada persyaratan apa jenis argumen akan menjadi.

Untuk perincian, Anda harus melihat pada posting yang lebih baru di milis swift-evolution.

gnasher729
sumber
7
"Posting baru di milis swift-evolusi" bukanlah referensi yang sangat jelas atau stabil. Apakah tidak ada arsip web dari milis?
Derek Elkins meninggalkan SE