Saya sadar bahwa konsep invarian ada di berbagai paradigma pemrograman. Sebagai contoh, invarian loop relevan dalam pemrograman OO, fungsional dan prosedural.
Namun, satu jenis yang sangat berguna yang ditemukan di OOP adalah invarian data tipe tertentu. Inilah yang saya sebut "invarian berbasis tipe" dalam judul. Sebagai contoh, suatu Fraction
tipe mungkin memiliki a numerator
dan denominator
, dengan invarian bahwa gcd mereka selalu 1 (yaitu fraksi dalam bentuk tereduksi). Saya hanya bisa menjamin ini dengan memiliki semacam enkapsulasi jenis, tidak membiarkan datanya diatur secara bebas. Sebagai imbalannya, saya tidak pernah harus memeriksa apakah itu berkurang, jadi saya dapat menyederhanakan algoritma seperti pemeriksaan kesetaraan.
Di sisi lain, jika saya hanya mendeklarasikan Fraction
tipe tanpa memberikan jaminan ini melalui enkapsulasi, saya tidak dapat dengan aman menulis fungsi apa pun pada tipe ini yang mengasumsikan bahwa fraksi berkurang, karena di masa depan orang lain dapat datang dan menambahkan cara untuk mendapatkan fraksi yang tidak berkurang.
Secara umum, kurangnya invarian semacam ini dapat menyebabkan:
- Algoritma yang lebih kompleks karena prasyarat perlu diperiksa / dipastikan di banyak tempat
- Pelanggaran KERING karena pra-kondisi berulang ini mewakili pengetahuan dasar yang sama (bahwa invarian harus benar)
- Harus menerapkan pra-kondisi melalui kegagalan runtime daripada jaminan waktu kompilasi
Jadi pertanyaan saya adalah apa jawaban pemrograman fungsional untuk jenis invarian ini. Apakah ada cara fungsional-idiomatis untuk mencapai hal yang kurang lebih sama? Atau ada beberapa aspek pemrograman fungsional yang membuat manfaatnya kurang relevan?
sumber
PrimeNumber
kelas. Akan terlalu mahal untuk melakukan beberapa pemeriksaan berlebih untuk primality untuk setiap operasi, tetapi ini bukan semacam tes yang dapat dilakukan pada waktu kompilasi. (Banyak operasi yang ingin Anda lakukan pada bilangan prima, katakanlah perkalian, jangan membentuk penutupan , yaitu hasil mungkin tidak dijamin prima. (Posting sebagai komentar karena saya sendiri tidak tahu pemrograman fungsional.)Jawaban:
Beberapa bahasa fungsional seperti OCaml memiliki mekanisme built-in untuk mengimplementasikan tipe data abstrak sehingga memberlakukan beberapa invarian . Bahasa yang tidak memiliki mekanisme seperti itu bergantung pada pengguna "tidak melihat di bawah karpet" untuk menegakkan invarian.
Tipe data abstrak dalam OCaml
Di OCaml, modul digunakan untuk menyusun program. Modul memiliki implementasi dan tanda tangan , yang terakhir adalah semacam ringkasan dari nilai dan tipe yang didefinisikan dalam modul, sementara yang pertama memberikan definisi aktual. Ini dapat secara longgar dibandingkan dengan diptych yang
.c/.h
akrab dengan programmer C.Sebagai contoh, kita dapat mengimplementasikan
Fraction
modul seperti ini:Definisi ini sekarang dapat digunakan seperti ini:
Siapa pun dapat menghasilkan nilai fraksi tipe secara langsung, melewati jaring pengaman bawaan
Fraction.make
:Untuk mencegah hal ini, dimungkinkan untuk menyembunyikan definisi konkret dari tipe
Fraction.t
seperti itu:Satu-satunya cara untuk membuat
AbstractFraction.t
adalah menggunakanAbstractFraction.make
fungsi.Tipe data abstrak dalam Skema
Bahasa Skema tidak memiliki mekanisme yang sama dengan tipe data abstrak seperti OCaml. Itu bergantung pada pengguna "tidak melihat di bawah karpet" untuk mencapai enkapsulasi.
Dalam Skema, sudah lazim untuk mendefinisikan predikat seperti
fraction?
mengenali nilai yang memberi kesempatan untuk memvalidasi input. Dalam pengalaman saya, penggunaan yang dominan adalah membiarkan pengguna memvalidasi inputnya, jika dia memalsukan suatu nilai, dan bukannya memvalidasi input dalam setiap panggilan pustaka.Namun ada beberapa strategi untuk menegakkan abstraksi nilai yang dikembalikan, seperti mengembalikan penutupan yang menghasilkan nilai ketika diterapkan atau mengembalikan referensi ke nilai dalam kumpulan yang dikelola oleh perpustakaan - tetapi saya tidak pernah melihat satu pun dari mereka dalam praktik.
sumber
Enkapsulasi bukan fitur yang disertakan dengan OOP. Bahasa apa pun yang mendukung modularisasi yang tepat memilikinya.
Berikut kira-kira cara Anda melakukannya di Haskell:
Sekarang, untuk membuat Rasional, Anda menggunakan fungsi rasio, yang memberlakukan invarian. Karena data tidak dapat diubah, Anda nantinya tidak dapat melanggar invarian.
Ini memang membuat Anda harus membayar sesuatu: pengguna tidak lagi dapat menggunakan deklarasi dekonstruksi yang sama seperti penggunaan penyebut dan pembilang.
sumber
Anda melakukannya dengan cara yang sama: membuat konstruktor yang memberlakukan kendala, dan setuju untuk menggunakan konstruktor itu setiap kali Anda membuat nilai baru.
Tetapi Karl, di OOP Anda tidak harus setuju untuk menggunakan konstruktor. Oh benarkah?
Faktanya, peluang untuk penyalahgunaan semacam ini lebih sedikit di FP. Anda harus meletakkan konstruktor sebagai yang terakhir, karena kekekalan. Saya berharap orang-orang akan berhenti memikirkan enkapsulasi sebagai semacam perlindungan terhadap kolega yang tidak kompeten, atau sebagai penghindaran kebutuhan untuk mengomunikasikan kendala. Itu tidak melakukan itu. Itu hanya membatasi tempat Anda harus memeriksa. Pemrogram FP yang baik juga menggunakan enkapsulasi. Itu hanya datang dalam bentuk mengkomunikasikan beberapa fungsi yang disukai untuk membuat jenis modifikasi tertentu.
sumber