Ya, pertanyaan yang sangat mudah di permukaan. Tetapi jika Anda meluangkan waktu untuk memikirkannya sampai akhir, Anda masuk ke kedalaman teori jenis yang tak terukur. Dan teori tipe juga menatap Anda.
Pertama, tentu saja, Anda sudah mengetahui dengan benar bahwa F # tidak memiliki kelas tipe, dan itulah sebabnya. Tapi Anda mengusulkan antarmuka Mappable
. Ok, mari kita lihat itu.
Katakanlah kita dapat mendeklarasikan antarmuka semacam itu. Bisakah Anda bayangkan seperti apa tanda tangannya?
type Mappable =
abstract member map : ('a -> 'b) -> 'f<'a> -> 'f<'b>
Di mana f
jenis mengimplementasikan antarmuka. Oh tunggu! F # tidak memilikinya juga! Berikut f
adalah variabel tipe yang lebih tinggi, dan F # tidak memiliki jenis yang lebih tinggi sama sekali. Tidak ada cara untuk mendeklarasikan fungsi f : 'm<'a> -> 'm<'b>
atau sesuatu seperti itu.
Tapi ok, katakanlah kita bisa mengatasi rintangan itu juga. Dan sekarang kita memiliki antarmuka Mappable
yang dapat diimplementasikan oleh List
, Array
, Seq
, dan wastafel dapur. Tapi tunggu! Sekarang kita memiliki metode alih-alih fungsi, dan metode tidak menyusun dengan baik! Mari kita lihat menambahkan 42 ke setiap elemen daftar bersarang:
// Good ol' functions:
add42 nestedList = nestedList |> List.map (List.map ((+) 42))
// Using an interface:
add42 nestedList = nestedList.map (fun l -> l.map ((+) 42))
Lihat: sekarang kita harus menggunakan ekspresi lambda! Tidak ada cara untuk meneruskan .map
implementasi ini ke fungsi lain sebagai nilai. Secara efektif akhir dari "berfungsi sebagai nilai" (dan ya, saya tahu, menggunakan lambda tidak terlihat sangat buruk dalam contoh ini, tapi percayalah, itu menjadi sangat jelek)
Tapi tunggu, kita masih belum selesai. Sekarang ini adalah pemanggilan metode, ketik inferensi tidak berfungsi! Karena tipe tanda tangan metode .NET tergantung pada jenis objek, tidak ada cara bagi kompilator untuk menyimpulkan keduanya. Ini sebenarnya adalah masalah yang sangat umum yang dialami pemula ketika beroperasi dengan. Dan satu-satunya obat adalah dengan memberikan tanda tangan jenis:
add42 (nestedList : #Mappable) = nestedList.map (fun l -> l.map ((+) 42))
Oh, tapi ini masih belum cukup! Meskipun saya telah memberikan tanda tangan untuk nestedList
dirinya sendiri, saya belum memberikan tanda tangan untuk parameter lambda l
. Seperti apa tanda tangan itu? Apakah Anda akan mengatakannya fun (l: #Mappable) -> ...
? Oh, dan sekarang kita akhirnya mendapatkan peringkat-N jenis, karena Anda lihat, #Mappable
adalah jalan pintas untuk "semua jenis 'a
seperti itu 'a :> Mappable
" - yaitu ekspresi lambda yang sendiri generik.
Atau, sebagai alternatif, kita bisa kembali ke kebaikan yang lebih tinggi dan mendeklarasikan tipe yang nestedList
lebih tepat:
add42 (nestedList : 'f<'a<'b>> where 'f :> Mappable, 'a :> Mappable) = ...
Tapi ok, mari kita kesampingkan inferensi tipe untuk saat ini dan kembali ke ekspresi lambda dan bagaimana kita sekarang tidak bisa meneruskan map
sebagai nilai ke fungsi lain. Katakanlah kita memperluas sedikit sintaks untuk memungkinkan seperti apa yang Elm lakukan dengan bidang rekaman:
add42 nestedList = nestedList.map (.map ((+) 42))
Seperti apa jadinya .map
? Itu harus menjadi tipe kendala , seperti di Haskell!
.map : Mappable 'f => ('a -> 'b) -> 'f<'a> -> 'f<'b>
Wow, baiklah. Mengesampingkan fakta bahwa .NET bahkan tidak mengizinkan jenis seperti itu ada, secara efektif kita baru saja kembali kelas jenis!
Tetapi ada alasan bahwa F # tidak memiliki kelas tipe di tempat pertama. Banyak aspek dari alasan itu dijelaskan di atas, tetapi cara yang lebih ringkas untuk mengatakannya adalah: kesederhanaan .
Untuk Anda lihat, ini adalah bola benang. Setelah Anda memiliki kelas tipe, Anda harus memiliki kendala, jenis yang lebih tinggi, peringkat-N (atau setidaknya peringkat-2), dan sebelum Anda menyadarinya, Anda meminta jenis yang tidak tepat, fungsi tipe, GADT, dan semua sisanya.
Tapi Haskell membayar harga untuk semua barang. Ternyata tidak ada cara yang baik untuk menyimpulkan semua hal itu. Jenis yang lebih baik agak bekerja, tetapi kendala sudah agak tidak. Peringkat-N - bahkan tidak memimpikannya. Dan bahkan ketika itu berhasil, Anda mendapatkan kesalahan ketik yang Anda harus memiliki PhD untuk mengerti Dan itu sebabnya di Haskell Anda dengan lembut didorong untuk menaruh tanda tangan ketik pada segalanya. Yah, tidak semuanya - semuanya, tapi benar-benar hampir semuanya. Dan di mana Anda tidak menaruh tanda tangan tipe (misalnya di dalam let
dan where
) - kejutan-kejutan, tempat-tempat itu sebenarnya monomorf, jadi Anda pada dasarnya kembali ke tanah F # yang sederhana.
Di F #, di sisi lain, tipe tanda tangan jarang, kebanyakan hanya untuk dokumentasi atau untuk .NET interop. Di luar kedua kasus itu, Anda dapat menulis program kompleks besar di F # dan tidak menggunakan tipe tanda tangan sekali pun. Jenis inferensi berfungsi dengan baik, karena tidak ada yang terlalu rumit atau ambigu untuk ditangani.
Dan ini adalah keuntungan besar F # atas Haskell. Ya, Haskell memungkinkan Anda mengekspresikan hal-hal yang sangat kompleks dengan cara yang sangat tepat, itu bagus. Tapi F # membuat Anda menjadi sangat plin-plan, hampir seperti Python atau Ruby, dan masih ada kompiler yang menangkap Anda jika tersandung.