Fungsi ML dari daftar polimorfik ke daftar polimorfik

8

Saya belajar pemrograman dalam ML (OCaml), dan sebelumnya saya bertanya tentang fungsi tipe ML'a -> 'b . Sekarang saya sudah bereksperimen sedikit dengan fungsi tipe 'a list -> 'b list. Ada beberapa contoh sederhana yang jelas:

let rec loop l = loop l
let return_empty l = []
let rec loop_if_not_empty = function [] -> []
                                   | l -> loop_if_not_empty l

Apa yang saya tidak tahu adalah bagaimana membuat fungsi yang melakukan sesuatu selain mengembalikan daftar kosong atau loop (tanpa menggunakan fungsi pustaka). Bisakah ini dilakukan? Apakah ada cara untuk mengembalikan daftar yang tidak kosong?

Sunting: Ya, jika saya memiliki fungsi tipe 'a -> 'b, maka saya bisa membuat yang lain, atau fungsi tipe 'a list -> 'b list, tapi yang saya ingin tahu di sini adalah bagaimana membuat yang pertama.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
1
Seperti pertanyaan sebelumnya, harap targetkan program pembelajaran siswa CS101 dalam jawaban Anda, bukan tipe teoretik yang mungkin menginspirasi jawaban Anda untuknya nanti.
Gilles 'SO- stop being evil'
Perhatikan bahwa jika Anda memiliki fungsi f dengan tipe ini mengembalikan daftar yang tidak kosong, maka a -> List.hd (f [a]) yang menyenangkan akan memiliki tipe 'a ->' b tanpa tanpa penghentian atau menaikkan pengecualian.
gallais

Jawaban:

6

Nah, sesuatu yang dikenal sebagai parametrik memberi tahu kita bahwa jika kita menganggap subset murni ML (yaitu, tidak ada rekursi tak terbatas, refdan semua hal aneh), tidak ada cara untuk mendefinisikan fungsi dengan tipe ini selain yang mengembalikan yang kosong daftar.

Ini semua dimulai dengan makalah Wadler “ Teorema gratis! ” Makalah ini, pada dasarnya, memberi tahu kita dua hal:

  1. Jika kita mempertimbangkan bahasa pemrograman yang memenuhi kondisi tertentu, kita dapat menyimpulkan beberapa teorema keren hanya dengan melihat tipe tanda tangan dari fungsi polimorfik (ini disebut Parametricity Theorem).
  2. ML (tanpa rekursi tak terbatas, refdan semua hal aneh itu) memenuhi kondisi itu.

Dari Parametricity Teorema kita tahu bahwa jika kita memiliki fungsi f : 'a list -> 'b list, maka untuk semua 'a, 'b, 'c, 'ddan untuk semua fungsi g : 'a -> 'c, h : 'b -> 'dkita memiliki:

map h ∘ f = f ∘ map g

(Catatan, fdi sebelah kiri memiliki tipe 'a list -> 'b listdan fdi sebelah kanan adalah 'c list -> 'd list.)

Kita bebas memilih apa pun yang gkita suka, jadi mari 'a = 'cdan g = id. Sekarang karena map id = id(mudah dibuktikan dengan induksi pada definisi map), kami memiliki:

map h ∘ f = f

Sekarang biarkan 'b = 'd = booldan h = not. Mari kita asumsikan untuk beberapa zs : bool listhal itu terjadi f zs ≠ [] : bool list. Hal ini jelas bahwa map not ∘ f = ftidak tidak tahan, karena

(map not ∘ f) zs ≠ f zs

Jika elemen pertama dari daftar di sebelah kanan adalah true, maka di sebelah kiri elemen pertama adalah falsedan sebaliknya!

Ini artinya, anggapan kita salah dan f zs = []. Sudahkah kita selesai? Tidak.

Kami berasumsi bahwa 'badalah bool. Kami telah menunjukkan bahwa ketika fdipanggil dengan tipe f : 'a list -> bool listuntuk apa pun 'a, fharus selalu mengembalikan daftar kosong. Mungkinkah ketika kita memanggil fkarena f : 'a list -> unit listmengembalikan sesuatu yang berbeda? Intuisi kita memberi tahu kita bahwa ini omong kosong: kita tidak bisa menulis dalam ML murni suatu fungsi yang selalu mengembalikan daftar kosong ketika kita ingin memberi kita daftar boolean dan mungkin mengembalikan daftar yang tidak kosong kalau tidak! Tapi ini bukan bukti.

Apa yang ingin kita katakan adalah bahwa fadalah seragam : jika selalu mengembalikan daftar kosong untuk bool list, maka harus mengembalikan daftar kosong untuk unit listdan, secara umum, setiap 'a list. Inilah tepatnya poin kedua dalam daftar peluru di awal jawaban saya.

Makalah ini memberitahu kita bahwa di ML fharus mengambil terkait nilai-nilai ke terkait yang. Saya tidak akan merinci tentang hubungan, cukup untuk mengatakan bahwa daftar terkait jika dan hanya jika mereka memiliki panjang yang sama dan unsur-unsurnya terkait pasangan (yaitu, [x_1, x_2, ..., x_m]dan [y_1, y_2, ..., y_n]terkait jika dan hanya jika m = ndan x_1terkait dengan y_1dan x_2terkait dengan y_2dan seterusnya). Dan bagian yang menyenangkan adalah, dalam kasus kami, karena fbersifat polimorfik, kita dapat mendefinisikan hubungan apa pun pada elemen daftar!

Mari kita pilih 'a, 'bdan lihat f : 'a list -> 'b list. Sekarang lihat f : 'a list -> bool list; kami telah menunjukkan bahwa dalam hal ini fselalu mengembalikan daftar kosong. Kami sekarang mendalilkan bahwa semua elemen 'aterkait dengan diri mereka sendiri (ingat, kita dapat memilih hubungan apa pun yang kita inginkan), ini menyiratkan bahwa ada zs : 'a listyang terkait dengan dirinya sendiri. Seperti kita ketahui, fmengambil nilai terkait dengan yang terkait, ini berarti yang f zs : 'b listterkait dengan f zs : bool list, tetapi daftar kedua memiliki panjang sama dengan nol, dan karena yang pertama terkait dengan itu, juga kosong.


Untuk kelengkapan, saya akan menyebutkan bahwa ada bagian tentang dampak rekursi umum (kemungkinan non-terminasi) dalam makalah asli Wadler, dan ada juga makalah yang mengeksplorasi teorema bebas di hadapan seq.

Kiragagin
sumber
Sekarang saya menduga buktinya dapat dilakukan dalam satu langkah jika alih-alih melemahkan teorema parametrik dengan mempertimbangkan hubungan spesifik yang disebabkan oleh fungsi ( gdan hdalam kasus ini) langsung dengan hubungan umum yang dibuat khusus ...
kirelagin
Nitpick, parametricity tidak dimulai dengan makalah Wadler (yang mengklaim sebagai ringkasan pendekatan untuk mendefinisikan parametricity). Idenya kembali ke kertas Reynold "Jenis, Abstraksi dan Polimorfisme Parametrik." Idenya juga agak hadir dalam bukti Girard tentang normalisasi untuk Sistem F sejauh yang saya tahu.
Daniel Gratzer
4

Mari kita kembali ke objek yang lebih sederhana: Anda tidak dapat membangun objek jenis yang tepat 'akarena itu berarti objek ini xdapat digunakan di mana pun 'acocok. Dan itu berarti di mana-mana: sebagai integer, array, bahkan fungsi. Misalnya itu berarti Anda dapat melakukan hal-hal seperti x+2, x.(1)dan (x 5). Jenis ada persis untuk mencegah hal ini.

Ini ide yang sama yang berlaku dengan fungsi tipe 'a -> 'b, tetapi ada beberapa kasus di mana tipe ini bisa ada: ketika fungsi tidak pernah mengembalikan objek tipe 'b: ketika perulangan atau menaikkan pengecualian.

Ini juga berlaku untuk fungsi yang mengembalikan daftar. Jika fungsi Anda bertipe t -> 'b listdan Anda membangun objek bertipe tdan menerapkannya pada fungsi ini, maka itu berarti bahwa jika Anda berhasil mengakses elemen daftar ini maka Anda akan mengakses ke objek yang memiliki semua tipe. Jadi, Anda tidak dapat mengakses elemen daftar mana pun: daftarnya kosong atau ... tidak ada daftar.

Namun tipe tersebut 'a list -> 'b listmuncul dalam latihan biasa tapi itu hanya ketika Anda sudah memiliki fungsi tipe 'a -> 'b:

let rec map (f : 'a -> 'b) =
  function
  | [] -> []
  | x :: xs -> f x :: map f xs

Tapi Anda mungkin tahu yang ini.

val map : ('a -> 'b) -> 'a list -> 'b list
jmad
sumber
1
Ahli teori tipe lama kurang senang dengan jawaban ini. Oke, konteks variabel tipe yang tidak kosong adalah cara untuk memiliki fungsi yang secara harfiah bertipe 'a -> 'batau 'a list -> 'b list, tapi itu bukan pengamatan yang menarik. Sebenarnya saya akan mengedit pertanyaan untuk memperjelas ini bukan apa yang dipikirkan oleh siswa yang lebih muda belajar pemrograman.
Gilles 'SO- stop being evil'
Tetapi ahli teori tipe lama tahu bahwa ML tidak secara logis cacat. Jika Anda dapat menghasilkan fungsi f : 'a list -> 'b listdan tsehingga f t <> []maka program ini akan ketik-cek tetapi dapat melakukannya dengan cara lebih buruk daripada menaikkan pengecualian: let y = List.hd (f t) in (y y) (y + y.(0) + y.(0).(0)).
jmad
2

Teorema Parametrik dari “Teorema Gratis!” makalah memberitahu kita bahwa istilah ML memiliki properti yang sangat istimewa: jika kita melihat jenis istilah sebagai hubungan pada nilai-nilai jenis ini, maka nilai istilah ini akan terkait dengan dirinya sendiri. Berikut ini cara melihat jenis sebagai relasi:

  • Tipe fungsi 'a -> 'bsesuai dengan relasi yang didefinisikan dengan mengatakan bahwa dua fungsi terkait jika mereka mengambil nilai terkait ke nilai terkait (dengan asumsi 'adan 'bsesuai dengan beberapa relasi).
  • Tipe daftar 'a listsesuai dengan relasi yang didefinisikan dengan mengatakan bahwa dua daftar terkait jika mereka memiliki panjang yang sama dan elemen-elemen pencocokannya terkait (dengan asumsi 'asesuai dengan beberapa relasi).
  • (Sekarang bagian yang paling menarik.) Jenis polimorfik bersesuaian dengan hubungan didefinisikan dengan mengatakan bahwa dua nilai polimorfik terkait jika kita bisa memilih setiap dua jenis, setiap hubungan antara unsur-unsur jenis ini, mengganti semua contoh dari variabel tipe dengan ini hubungan, dan nilai yang dihasilkan masih akan terkait.

Berikut ini sebuah contoh. Misalkan kita memiliki istilah foo : 'a -> 'a. Teorema Parametrik mengatakan bahwa fooitu terkait dengan dirinya sendiri. Artinya, kita dapat memilih dua jenis, katakan,SEBUAH1 dan SEBUAH2, pilih mutlak hubungan apa pun SEBUAH antara elemen jenis ini, dan jika kita mengambil Sebuah1:SEBUAH1 dan Sebuah2:SEBUAH2, sehingga mereka terkait menurut SEBUAH, kemudian fooSebuah1 dan fooSebuah2 juga akan terkait sesuai dengan SEBUAH:

Sebuah1SEBUAHSebuah2fooSebuah1SEBUAHfooSebuah2.

Sekarang jika kita ambil relasinya SEBUAH menjadi bukan hubungan yang sewenang-wenang, tetapi suatu fungsi f:SEBUAH1SEBUAH2, yang di atas menjadi:

f(Sebuah1)=Sebuah2f(fooSebuah1)=fooSebuah2,

atau, dengan kata lain:

f(fooSebuah1)=foo(f(Sebuah1)),

yang persis teorema bebas untuk idfungsi: f . id = id . f.


Jika Anda melakukan langkah-langkah serupa untuk fungsi foo : 'a list -> 'b listAnda, Anda akan dapat memilih dua jenisSEBUAH1 dan SEBUAH2, hubungan apa pun SEBUAH di antara elemen-elemennya, ada dua jenis B1 dan B2, hubungan apa pun B antara elemen-elemen mereka, lalu ambil dua daftar, yang pertama terbuat dari elemen SEBUAH1, kedua terbuat dari elemen SEBUAH2, terapkan fungsi Anda ke kedua daftar (dapatkan daftar B1 dalam kasus pertama dan daftar B2 di yang kedua), dan hasilnya akan terkait, jika input terkait.

Sekarang kami menggunakan ini untuk membuktikan bahwa untuk dua jenis Adan Bfungsi foomengembalikan daftar kosong untuk input apa pun as : A list.

  • Membiarkan SEBUAH1=SEBUAH2= A dan biarkan SEBUAH menjadi hubungan identitas, dengan demikian daftar SEBUAH secara sepele terkait dengan dirinya sendiri.
  • Membiarkan B1= Ø, B2= B dan B hubungan apa pun di antara mereka (hanya ada satu, yang kosong, tetapi tidak masalah).
  • asterkait dengan dirinya sendiri (karena kami memilih hubungan identitas pada A), dengan demikian foo as : Ø listterkait dengan foo as : B list.
  • Kami tahu bahwa dua daftar hanya dapat dikaitkan jika panjangnya sama dan kami juga tahu bahwa yang pertama dari daftar yang dihasilkan harus kosong, karena tidak ada elemen dari Øtipe tersebut.

Karena itu, untuk apa pun A, Bdan as : A listkita harus foo as : B listmemiliki daftar kosong.

Kiragagin
sumber