Saya mulai belajar Haskell . Saya sangat baru dalam hal ini, dan saya hanya membaca beberapa buku online untuk memahami konstruksi dasarnya.
Salah satu 'meme' yang sering dibicarakan orang-orang, adalah keseluruhan "jika dikompilasi, itu akan berfungsi *" - yang saya pikir terkait dengan kekuatan sistem tipe.
Saya mencoba untuk memahami mengapa persis Haskell lebih baik dari lainnya bahasa statis diketik dalam hal ini.
Dengan kata lain, saya berasumsi di Jawa, Anda bisa melakukan sesuatu yang keji seperti mengubur
ArrayList<String>()
untuk mengandung sesuatu yang seharusnya ArrayList<Animal>()
. Hal keji di sini adalah adalah bahwa Anda string
mengandung elephant, giraffe
, dll, dan jika seseorang menempatkan di Mercedes
- compiler Anda tidak akan membantu Anda.
Jika saya melakukan lakukan ArrayList<Animal>()
kemudian, di beberapa titik kemudian dalam waktu, jika saya memutuskan program saya tidak benar-benar tentang hewan, ini tentang kendaraan, maka saya dapat mengubah, katakanlah, sebuah fungsi yang menghasilkan ArrayList<Animal>
untuk menghasilkan ArrayList<Vehicle>
dan IDE saya harus memberitahu saya di mana-mana ada adalah istirahat kompilasi.
Asumsi saya adalah bahwa inilah yang orang maksud dengan sistem tipe yang kuat , tetapi tidak jelas bagi saya mengapa Haskell lebih baik. Dengan kata lain, Anda dapat menulis Java yang baik atau buruk, saya berasumsi Anda dapat melakukan hal yang sama di Haskell (yaitu memasukkan hal-hal ke dalam string / int yang seharusnya benar-benar tipe data kelas satu).
Saya curiga saya kehilangan sesuatu yang penting / mendasar.
Saya akan sangat senang ditunjukkan kesalahan cara saya!
sumber
Maybe
hanya menyebutkan menjelang akhir. Jika saya harus memilih satu hal yang sebaiknya dipinjam dari bahasa populer dari Haskell, ini dia. Itu ide yang sangat sederhana (jadi tidak terlalu menarik dari sudut pandang teoritis), tetapi ini saja akan membuat pekerjaan kita jauh lebih mudah.Jawaban:
Berikut adalah daftar fitur sistem tipe yang tidak teratur yang tersedia di Haskell dan tidak tersedia atau kurang bagus di Jawa (sepengetahuan saya, Java wrt yang diakui lemah)
Eq
uality mungkin diturunkan secara otomatis untuk tipe yang ditentukan pengguna oleh kompiler Haskell. Pada dasarnya cara melakukan hal ini adalah berjalan di atas struktur umum, sederhana yang mendasari setiap jenis yang ditetapkan pengguna dan mencocokkannya di antara nilai-nilai — bentuk yang sangat alami dari kesetaraan struktural.data Bt a = Here a | There (Bt (a, a))
. Pikirkan baik-baik tentang nilai validBt a
dan perhatikan bagaimana jenis itu bekerja. Ini rumit!IO
. Java mungkin sebenarnya memiliki tipe cerita abstrak yang lebih bagus, jujur saja, tapi saya tidak berpikir sampai Interfaces menjadi lebih populer adalah itu benar-benar benar.mtl
sistem pengetikan efek, fixpoints functor umum. Daftar ini terus berlanjut. Ada banyak hal yang paling baik diungkapkan pada jenis yang lebih tinggi dan sistem jenis yang relatif sedikit bahkan memungkinkan pengguna untuk membicarakan hal-hal ini.(+)
bersama-sama? OhInteger
, ok! Mari kita sebariskan kode yang benar sekarang!". Pada sistem yang lebih kompleks, Anda mungkin membuat batasan yang lebih menarik.mtl
perpustakaan didasarkan pada ide ini.(forall a. f a -> g a)
. Dalam lurus HM Anda dapat menulis sebuah fungsi di jenis ini, namun dengan jenis yang lebih tinggi-peringkat Anda menuntut fungsi seperti sebagai argumen seperti:mapFree :: (forall a . f a -> g a) -> Free f -> Free g
. Perhatikan bahwaa
variabel hanya terikat dalam argumen. Ini berarti bahwa definisi fungsimapFree
dapat memutuskan apaa
yang dipakai saat mereka menggunakannya, bukan penggunamapFree
.Jenis diindeks jenis dan jenis promosi . Saya menjadi sangat eksotis pada saat ini, tetapi ini memiliki penggunaan praktis dari waktu ke waktu. Jika Anda ingin menulis jenis pegangan yang terbuka atau tertutup maka Anda dapat melakukannya dengan sangat baik. Perhatikan dalam cuplikan berikut ini yang
State
merupakan tipe aljabar yang sangat sederhana yang nilainya ditingkatkan menjadi tipe-level juga. Kemudian, selanjutnya, kita dapat berbicara tentang konstruktor tipe sepertiHandle
mengambil argumen pada jenis tertentu sepertiState
. Sangat membingungkan untuk memahami semua detail, tetapi juga sangat benar.Representasi tipe Runtime yang berfungsi . Java terkenal karena memiliki penghapusan tipe dan memiliki fitur hujan pada parade beberapa orang. Namun, penghapusan tipe adalah cara yang tepat, seolah-olah Anda memiliki fungsi
getRepr :: a -> TypeRepr
maka paling tidak Anda melanggar parametrik. Yang lebih buruk adalah bahwa jika itu adalah fungsi yang dibuat pengguna yang digunakan untuk memicu paksaan yang tidak aman saat runtime ... maka Anda memiliki masalah keamanan yang besar .Typeable
Sistem Haskell memungkinkan pembuatan brankascoerce :: (Typeable a, Typeable b) => a -> Maybe b
. Sistem ini bergantung padaTypeable
diimplementasikan dalam kompiler (dan bukan userland) dan juga tidak dapat diberikan semantik bagus tanpa mekanisme typeclass Haskell dan hukum itu dijamin untuk mengikuti.Lebih dari ini, nilai sistem tipe Haskell juga berhubungan dengan bagaimana tipe menggambarkan bahasa. Berikut adalah beberapa fitur Haskell yang mendorong nilai melalui sistem tipe.
IO a
untuk mewakili perhitungan efek samping yang menghasilkan nilai tipea
. Ini adalah dasar dari sistem efek yang sangat bagus yang tertanam di dalam bahasa murni.null
. Semua orang tahu itunull
adalah kesalahan miliaran dolar dari bahasa pemrograman modern. Tipe aljabar, khususnya kemampuan untuk hanya menambahkan status "tidak ada" ke tipe yang Anda miliki dengan mengubah suatu tipeA
menjadi tipeMaybe A
, sepenuhnya mengurangi masalahnull
.Bt a
jenis dari sebelumnya dan mencoba untuk menulis fungsi untuk menghitung ukurannya:size :: Bt a -> Int
. Ini akan terlihat sepertisize (Here a) = 1
dansize (There bt) = 2 * size bt
. Secara operasional itu tidak terlalu rumit, tetapi perhatikan bahwa panggilan rekursif kesize
dalam persamaan terakhir terjadi pada tipe yang berbeda , namun definisi keseluruhan memiliki tipe umum yang bagussize :: Bt a -> Int
. Perhatikan bahwa ini adalah fitur yang memecah total inferensi, tetapi jika Anda memberikan tanda tangan jenis maka Haskell akan mengizinkannya.Saya bisa terus berjalan, tetapi daftar ini harus membuat Anda memulai, dan kemudian beberapa.
sumber
char *p = NULL;
, akan menjebak*p=1234
, tetapi tidak akan menjebakchar *q = p+5678;
atau*q = 1234;
null
diperlukan dalam aritmatika pointer, saya malah mengartikannya untuk mengatakan bahwa pointer aritmatika adalah tempat yang buruk untuk meng-host semantik bahasa Anda bukan karena nol tidak masih merupakan kesalahan.p = undefined
selamap
tidak dievaluasi. Lebih berguna, Anda dapat memasukkanundefined
semacam referensi yang bisa berubah, sekali lagi selama Anda tidak mengevaluasinya. Tantangan yang lebih serius adalah dengan perhitungan malas yang mungkin tidak berakhir, yang tentu saja tidak dapat diputuskan. Perbedaan utama adalah bahwa ini semua kesalahan pemrograman yang jelas , dan tidak pernah digunakan untuk mengekspresikan logika biasa.for
loop untuk mengimplementasikan fungsi yang sama, tetapi Anda tidak akan memiliki jaminan tipe statis yang sama, karenafor
loop tidak memiliki konsep tipe pengembalian.sumber
Di Haskell: integer, integer yang mungkin nol, integer yang nilainya berasal dari dunia luar, dan integer yang mungkin berupa string, semuanya adalah tipe yang berbeda - dan kompiler akan menegakkan ini . Anda tidak dapat mengkompilasi program Haskell yang gagal menghargai perbedaan-perbedaan ini.
(Namun, Anda dapat menghilangkan deklarasi tipe. Dalam kebanyakan kasus, kompiler dapat menentukan tipe paling umum untuk variabel Anda yang akan menghasilkan kompilasi yang berhasil. Bukankah itu rapi?)
sumber
Maybe
(misalnya JavaOptional
dan ScalaOption
), tetapi dalam bahasa-bahasa itu solusi setengah matang, karena Anda selalu dapat menetapkannull
ke variabel jenis itu, dan membiarkan program Anda meledak saat dijalankan- waktu. Ini tidak dapat terjadi dengan Haskell [1], karena tidak ada nilai nol , jadi Anda tidak bisa menipu. ([1]: sebenarnya, Anda dapat menghasilkan kesalahan yang mirip dengan NullPointerException menggunakan fungsi parsial sepertifromJust
ketika Anda memilikiNothing
, tetapi fungsi-fungsi itu mungkin disukai).IO Integer
akan lebih dekat dengan 'subprogram yang, ketika dijalankan, memberikan integer'? As a) dalammain = c >> c
nilai yang dikembalikan oleh pertamac
mungkin berbeda kemudian oleh keduac
sementaraa
akan memiliki nilai yang sama terlepas dari posisinya (selama kita berada dalam ruang lingkup tunggal) b) ada jenis yang menunjukkan nilai dari dunia luar untuk menegakkan sanitasinya (Yaitu untuk tidak menempatkan mereka secara langsung tetapi periksa dulu apakah input dari pengguna sudah benar / tidak berbahaya).Banyak orang telah mendaftar hal-hal baik tentang Haskell. Tetapi dalam menjawab pertanyaan spesifik Anda "mengapa sistem tipe membuat program lebih benar?", Saya menduga jawabannya adalah "parametrik polimorfisme".
Pertimbangkan fungsi Haskell berikut:
Ada secara harfiah hanya satu cara yang mungkin untuk melaksanakan fungsi ini. Hanya dengan jenis tanda tangan, saya dapat mengetahui dengan tepat apa fungsi ini, karena hanya ada satu hal yang dapat dilakukan. [OK, tidak cukup, tapi hampir!]
Berhentilah dan pikirkan hal itu sejenak. Itu sebenarnya masalah besar! Itu berarti jika saya menulis fungsi dengan tanda tangan ini, sebenarnya tidak mungkin untuk melakukan fungsi selain apa yang saya maksudkan. (Jenis tanda tangan itu sendiri masih bisa salah, tentu saja. Tidak ada bahasa pemrograman yang akan mencegah semua bug.)
Pertimbangkan fungsi ini:
Fungsi ini tidak mungkin . Anda benar-benar tidak dapat mengimplementasikan fungsi ini. Saya bisa mengatakan itu hanya dari jenis tanda tangan.
Seperti yang Anda lihat, tanda tangan tipe Haskell memberi tahu Anda banyak sekali!
Bandingkan dengan C #. (Maaf, Java saya agak berkarat.)
Ada beberapa hal yang bisa dilakukan metode ini:
in2
sebagai hasilnya.Sebenarnya, Haskell memiliki tiga opsi ini juga. Tetapi C # juga memberi Anda opsi tambahan:
in2
sebelum mengembalikannya. (Haskell tidak memiliki modifikasi di tempat.)Refleksi adalah palu besar; menggunakan refleksi, saya dapat membuat
TY
objek baru dari udara tipis, dan mengembalikannya! Saya dapat memeriksa kedua objek, dan melakukan berbagai tindakan tergantung pada apa yang saya temukan. Saya dapat melakukan modifikasi sembarang untuk kedua objek yang dilewatkan.I / O adalah palu besar yang serupa. Kode dapat menampilkan pesan kepada pengguna, atau membuka koneksi database, atau memformat ulang harddisk Anda, atau apa pun, sungguh.
Fungsi Haskell
foobar
, sebaliknya, hanya dapat mengambil beberapa data dan mengembalikan data itu, tidak berubah. Itu tidak bisa "melihat" data, karena tipenya tidak diketahui pada waktu kompilasi. Itu tidak dapat membuat data baru, karena ... yah, bagaimana Anda membuat data dari jenis apa pun yang mungkin? Anda perlu refleksi untuk itu. Itu tidak dapat melakukan I / O, karena tanda tangan jenis tidak menyatakan bahwa I / O sedang dilakukan. Jadi ia tidak dapat berinteraksi dengan sistem file atau jaringan, atau bahkan menjalankan utas dalam program yang sama! (Yaitu, dijamin 100% aman.)Seperti yang Anda lihat, dengan tidak membiarkan Anda melakukan banyak hal, Haskell memungkinkan Anda untuk membuat jaminan yang sangat kuat tentang apa yang sebenarnya kode Anda lakukan. Begitu ketat, pada kenyataannya, bahwa (untuk kode yang benar-benar polimorfik) biasanya hanya ada satu cara yang memungkinkan potongan-potongan tersebut dapat saling menyatu.
(Untuk lebih jelasnya: Masih mungkin untuk menulis fungsi Haskell di mana tanda tangan jenis tidak memberi tahu Anda banyak.
Int -> Int
Bisa saja tentang apa pun. Tetapi meskipun demikian, kita tahu bahwa input yang sama akan selalu menghasilkan output yang sama dengan kepastian 100%. Java bahkan tidak menjamin itu!)sumber
fubar :: a -> b
, bukan? (Ya, saya sadarunsafeCoerce
. Saya berasumsi kita tidak membicarakan apa pun dengan nama "tidak aman", dan pendatang baru juga tidak perlu khawatir!: D)foobar :: x
cukup tidak dapat diterapkan ...x -> y -> y
ini dapat diterapkan dengan sempurna. Jenisnya(x -> y) -> y
bukan. Jenis inix -> y -> y
mengambil dua input, dan mengembalikan yang kedua. Jenis ini(x -> y) -> y
mengambil fungsi yang beroperasi padax
, dan entah bagaimana harus membuaty
keluar dari itu ...Pertanyaan SO terkait .
Tidak, Anda benar-benar tidak bisa - setidaknya tidak dengan cara yang sama seperti yang dilakukan Java. Di Jawa, hal semacam ini terjadi:
dan Java akan dengan senang hati mencoba menggunakan non-String Anda sebagai String. Haskell tidak mengizinkan hal semacam ini, menghilangkan seluruh kelas kesalahan runtime.
null
adalah bagian dari sistem tipe (asNothing
) sehingga perlu secara eksplisit diminta dan ditangani, menghilangkan seluruh kelas kesalahan runtime lainnya.Ada banyak manfaat halus lainnya juga - terutama di sekitar kelas reuse dan type - yang saya tidak memiliki keahlian untuk cukup tahu untuk berkomunikasi.
Namun, sebagian besar karena sistem tipe Haskell memungkinkan banyak ekspresi. Anda dapat melakukan banyak hal dengan hanya beberapa aturan. Pertimbangkan pohon Haskell yang selalu ada:
Anda telah mendefinisikan seluruh pohon biner umum (dan dua konstruktor data) dalam satu baris kode yang cukup mudah dibaca. Semua hanya menggunakan beberapa aturan (memiliki jenis penjumlahan dan jenis produk ). Itu adalah 3-4 kode file dan kelas di Jawa.
Terutama di antara mereka yang cenderung memuja sistem tipe, keringkasan / keanggunan semacam ini sangat dihargai.
sumber
interface
s yang dapat ditambahkan setelah-fakta, dan mereka tidak "lupa" jenis yang mengimplementasikannya. Yaitu, Anda dapat memastikan bahwa dua argumen untuk suatu fungsi memiliki tipe yang sama, tidak seperti denganinterface
s di mana duaList<String>
s mungkin memiliki implementasi yang berbeda. Anda secara teknis dapat melakukan sesuatu yang sangat mirip di Jawa dengan menambahkan parameter tipe ke setiap antarmuka, tetapi 99% dari antarmuka yang ada tidak melakukannya dan Anda akan membingungkan teman-teman Anda.Object
.any
. Haskell juga tidak akan menghentikanmu melakukan ini, karena ... yah, ia memiliki string. Haskell dapat memberi Anda alat, itu tidak bisa secara paksa menghentikan Anda dari melakukan hal-hal bodoh jika Anda bersikeras pada Greenspunning cukup dari seorang juru bahasa untuk menemukan kembalinull
dalam konteks bersarang. Tidak ada bahasa yang bisa.Ini sebagian besar benar dengan program kecil. Haskell mencegah Anda membuat kesalahan yang mudah dalam bahasa lain (misalnya membandingkan
Int32
danWord32
dan sesuatu meledak), tetapi itu tidak mencegah Anda dari semua kesalahan.Haskell sebenarnya membuat refactoring jauh lebih mudah. Jika program Anda sebelumnya benar dan itu ketik pemeriksaan, ada kemungkinan adil itu akan tetap benar setelah perbaikan kecil.
Jenis-jenis di Haskell cukup ringan, sehingga mudah untuk mendeklarasikan jenis-jenis baru. Ini berbeda dengan bahasa seperti Rust, di mana semuanya hanya sedikit lebih rumit.
Haskell memiliki banyak fitur di luar jumlah sederhana dan jenis produk; ia memiliki tipe yang dikuantifikasi secara universal (misalnya
id :: a -> a
) juga. Anda juga dapat membuat tipe rekaman yang berisi fungsi, yang sangat berbeda dari bahasa seperti Java atau Rust.GHC juga dapat menurunkan beberapa instance berdasarkan tipe saja, dan sejak munculnya generik, Anda dapat menulis fungsi yang generik di antara tipe. Ini cukup nyaman, dan lebih lancar daripada yang sama di Jawa.
Perbedaan lainnya adalah bahwa Haskell cenderung memiliki kesalahan tipe yang relatif baik (setidaknya pada saat penulisan). Jenis inferensi Haskell canggih, dan sangat jarang Anda perlu memberikan anotasi jenis untuk mendapatkan sesuatu untuk dikompilasi. Ini berbeda dengan Rust, di mana inferensi tipe kadang-kadang dapat membutuhkan anotasi bahkan ketika kompiler pada prinsipnya dapat menyimpulkan tipe.
Akhirnya, Haskell memiliki kacamata ketik, di antaranya adalah monad yang terkenal. Monads merupakan cara yang sangat baik untuk menangani kesalahan; mereka pada dasarnya memberi Anda hampir semua kenyamanan
null
tanpa debugging mengerikan dan tanpa memberikan keamanan jenis apa pun. Jadi kemampuan untuk menulis fungsi pada tipe-tipe ini sebenarnya sangat penting ketika mendorong kita untuk menggunakannya!Itu mungkin benar, tetapi tidak ada poin penting: titik di mana Anda mulai menembak diri sendiri di kaki Haskell lebih jauh dari titik ketika Anda mulai menembak diri sendiri di kaki di Jawa.
sumber