Sebagian besar sumber mendefinisikan fungsi murni sebagai memiliki dua properti berikut:
- Nilai kembalinya sama untuk argumen yang sama.
- Evaluasi tidak memiliki efek samping.
Ini adalah kondisi pertama yang mengkhawatirkan saya. Dalam kebanyakan kasus, mudah untuk menilai. Pertimbangkan fungsi-fungsi JavaScript berikut (seperti yang ditunjukkan dalam artikel ini )
Murni:
const add = (x, y) => x + y;
add(2, 4); // 6
Najis:
let x = 2;
const add = (y) => {
return x += y;
};
add(4); // x === 6 (the first time)
add(4); // x === 10 (the second time)
Sangat mudah untuk melihat bahwa fungsi ke-2 akan memberikan output yang berbeda untuk panggilan berikutnya, sehingga melanggar kondisi pertama. Dan karenanya, itu tidak murni.
Bagian ini saya dapatkan.
Sekarang, untuk pertanyaan saya, pertimbangkan fungsi ini yang mengubah jumlah tertentu dalam dolar menjadi euro:
(EDIT - Menggunakan const
di baris pertama. Digunakan let
sebelumnya secara tidak sengaja.)
const exchangeRate = fetchFromDatabase(); // evaluates to say 0.9 for today;
const dollarToEuro = (x) => {
return x * exchangeRate;
};
dollarToEuro(100) //90 today
dollarToEuro(100) //something else tomorrow
Anggaplah kita mengambil nilai tukar dari db dan itu berubah setiap hari.
Sekarang, tidak peduli berapa kali saya memanggil fungsi ini hari ini , itu akan memberi saya output yang sama untuk input 100
. Namun, itu mungkin memberi saya hasil yang berbeda besok. Saya tidak yakin apakah ini melanggar kondisi pertama atau tidak.
TKI, fungsi itu sendiri tidak mengandung logika untuk mengubah input, tetapi bergantung pada konstanta eksternal yang mungkin berubah di masa depan. Dalam hal ini, sangat pasti akan berubah setiap hari. Dalam kasus lain, itu mungkin terjadi; mungkin tidak.
Bisakah kita memanggil fungsi seperti fungsi murni. Jika jawabannya TIDAK, bagaimana kita bisa menolaknya menjadi satu?
sumber
function myNumber(n) { this.n = n; }; myNumber.prototype.valueOf = function() { console.log('impure'); return this.n; }; const n = new myNumber(42); add(n, 1);
(x) => {return x * 0.9;}
. Besok, Anda akan memiliki fungsi berbeda yang juga murni, mungkin(x) => {return x * 0.89;}
. Perhatikan bahwa setiap kali Anda menjalankannya(x) => {return x * exchangeRate;}
menciptakan fungsi baru , dan fungsi itu murni karenaexchangeRate
tidak dapat berubah.const dollarToEuro = (x, exchangeRate) => { return x * exchangeRate; };
untuk fungsi murni,Its return value is the same for the same arguments.
harus selalu memegang, 1 detik, 1 dekade .. nanti tidak peduli apaJawaban:
Nilai
dollarToEuro
pengembalian tergantung pada variabel luar yang bukan argumen; oleh karena itu, fungsinya tidak murni.Salah satu opsi adalah lewat
exchangeRate
. Dengan cara ini, setiap kali ada argumen(something, somethingElse)
, output dijamin menjadisomething * somethingElse
:Perhatikan bahwa untuk pemrograman fungsional, Anda harus menghindari
let
- selalu gunakanconst
untuk menghindari penugasan kembali.sumber
const add = x => y => x + y; const one = add(42);
Berikut baikadd
danone
adalah fungsi murni.const foo = 42; const add42 = x => x + foo;
<- ini adalah fungsi murni lain, yang lagi-lagi menggunakan variabel bebas.dollarToEuro
fungsi dalam contoh dalam jawaban Anda tidak murni karena tergantung pada variabel bebasexchangeRate
. Itu tidak masuk akal. Seperti yang ditunjukkan zerkms, kemurnian suatu fungsi tidak ada hubungannya dengan apakah ia memiliki variabel bebas atau tidak. Namun, zerkms juga salah karena dia percaya bahwadollarToEuro
fungsinya tidak murni karena itu tergantung dariexchangeRate
mana yang berasal dari database. Dia mengatakan itu tidak murni karena "itu tergantung pada IO secara transitif."dollarToEuro
itu tidak murni karenaexchangeRate
merupakan variabel bebas. Ini menunjukkan bahwa jikaexchangeRate
bukan variabel bebas, yaitu jika itu adalah argumen, makadollarToEuro
akan menjadi murni. Oleh karena itu, ini menunjukkan bahwadollarToEuro(100)
itu tidak murni tetapidollarToEuro(100, exchangeRate)
murni. Itu jelas tidak masuk akal karena dalam kedua kasus Anda tergantung padaexchangeRate
yang berasal dari database. Satu-satunya perbedaan adalah apakahexchangeRate
variabel bebas di dalamdollarToEuro
fungsi.Secara teknis, setiap program yang Anda jalankan di komputer tidak murni karena pada akhirnya mengkompilasi ke instruksi seperti "pindahkan nilai ini ke
eax
" dan "tambahkan nilai ini ke konteneax
", yang tidak murni. Itu tidak terlalu membantu.Sebaliknya, kami berpikir tentang kemurnian menggunakan kotak hitam . Jika beberapa kode selalu menghasilkan output yang sama ketika diberi input yang sama maka itu dianggap murni. Dengan definisi ini, fungsi berikut ini juga murni meskipun secara internal ia menggunakan tabel memo tidak murni.
Kami tidak peduli dengan internal karena kami menggunakan metodologi kotak hitam untuk memeriksa kemurnian. Demikian pula, kami tidak peduli bahwa semua kode pada akhirnya dikonversi menjadi instruksi mesin yang tidak murni karena kami berpikir tentang kemurnian menggunakan metodologi kotak hitam. Internal tidak penting.
Sekarang, pertimbangkan fungsi berikut.
Adalah
greet
fungsinya murni atau tidak murni? Dengan metodologi kotak hitam kami, jika kami memberikan input yang sama (misalnyaWorld
) maka selalu mencetak output yang sama ke layar (yaituHello World!
). Dalam pengertian itu, bukankah itu murni? Tidak, tidak. Alasan itu tidak murni adalah karena kami menganggap mencetak sesuatu ke layar sebagai efek samping. Jika kotak hitam kami menghasilkan efek samping maka itu tidak murni.Apa itu efek samping? Di sinilah konsep transparansi referensial berguna. Jika suatu fungsi secara transparan transparan maka kita selalu dapat mengganti aplikasi dari fungsi itu dengan hasilnya. Perhatikan bahwa ini tidak sama dengan fungsi inlining .
Dalam fungsi inlining, kami mengganti aplikasi fungsi dengan tubuh fungsi tanpa mengubah semantik program. Namun, fungsi transparan referensial selalu dapat diganti dengan nilai kembali tanpa mengubah semantik program. Perhatikan contoh berikut.
Di sini, kami menggariskan definisi
greet
dan tidak mengubah semantik program.Sekarang, pertimbangkan program berikut.
Di sini, kami mengganti aplikasi
greet
fungsi dengan nilai kembali dan itu memang mengubah semantik program. Kami tidak lagi mencetak salam ke layar. Itulah alasan mengapa pencetakan dianggap sebagai efek samping, dan itulah sebabnyagreet
fungsinya tidak murni. Ini tidak transparan secara referensial.Sekarang, mari kita pertimbangkan contoh lain. Pertimbangkan program berikut.
Jelas,
main
fungsinya tidak murni. Namun, apakahtimeDiff
fungsinya murni atau tidak murni? Meskipun itu tergantungserverTime
yang berasal dari panggilan jaringan tidak murni, itu masih transparan secara referensial karena ia mengembalikan output yang sama untuk input yang sama dan karena itu tidak memiliki efek samping.Zerkms mungkin akan tidak setuju dengan saya dalam hal ini. Dalam jawabannya , ia mengatakan bahwa
dollarToEuro
fungsi dalam contoh berikut tidak murni karena "itu tergantung pada IO secara transitif."Saya harus tidak setuju dengannya karena fakta bahwa
exchangeRate
databasenya tidak relevan. Ini detail internal dan metodologi kotak hitam kami untuk menentukan kemurnian fungsi tidak peduli dengan detail internal.Dalam bahasa yang murni fungsional seperti Haskell, kami memiliki jalan keluar untuk mengeksekusi efek IO yang arbitrer. Ini disebut
unsafePerformIO
, dan seperti namanya jika Anda tidak menggunakannya dengan benar maka itu tidak aman karena dapat merusak transparansi referensial. Namun, jika Anda tahu apa yang Anda lakukan maka itu sangat aman untuk digunakan.Ini umumnya digunakan untuk memuat data dari file konfigurasi di dekat awal program. Memuat data dari file konfigurasi adalah operasi IO yang tidak murni. Namun, kami tidak ingin terbebani dengan mengirimkan data sebagai input ke setiap fungsi. Makanya, jika kita gunakan
unsafePerformIO
maka kita dapat memuat data di tingkat atas dan semua fungsi murni kita dapat bergantung pada data konfigurasi global yang tidak dapat diubah.Perhatikan bahwa hanya karena suatu fungsi bergantung pada beberapa data yang dimuat dari file konfigurasi, database, atau panggilan jaringan, tidak berarti bahwa fungsi tersebut tidak murni.
Namun, mari kita perhatikan contoh asli Anda yang memiliki semantik berbeda.
Di sini, saya berasumsi bahwa karena
exchangeRate
tidak didefinisikan sebagaiconst
, itu akan dimodifikasi ketika program sedang berjalan. Jika itu yang terjadi makadollarToEuro
pasti fungsi yang tidak murni karena ketikaexchangeRate
dimodifikasi, itu akan merusak transparansi referensial.Namun, jika
exchangeRate
variabel tidak dimodifikasi dan tidak akan pernah dimodifikasi di masa mendatang (yaitu jika itu adalah nilai konstan), maka meskipun itu didefinisikan sebagailet
, itu tidak akan merusak transparansi referensial. Dalam hal ini,dollarToEuro
memang fungsi murni.Perhatikan bahwa nilai
exchangeRate
dapat berubah setiap kali Anda menjalankan program lagi dan itu tidak akan merusak transparansi referensial. Itu hanya merusak transparansi referensial jika itu berubah saat program sedang berjalan.Misalnya, jika Anda menjalankan
timeDiff
contoh saya beberapa kali maka Anda akan mendapatkan nilai yang berbeda untukserverTime
dan karenanya hasil yang berbeda. Namun, karena nilaiserverTime
tidak pernah berubah saat program sedang berjalan,timeDiff
fungsinya murni.sumber
const
dalam contoh saya.const
makadollarToEuro
fungsinya memang murni. Satu-satunya cara nilaiexchangeRate
akan berubah adalah jika Anda menjalankan program lagi. Dalam hal ini, proses lama dan proses baru berbeda. Oleh karena itu, itu tidak melanggar transparansi referensial. Ini seperti memanggil fungsi dua kali dengan argumen yang berbeda. Argumen mungkin berbeda tetapi dalam fungsi nilai argumen tetap konstan.eax
dihapus - melalui suatu beban atau yang jelas - kode tetap deterministik terlepas dari apa lagi yang terjadi dan karenanya murni. Kalau tidak, jawaban yang sangat komprehensif.Sebuah jawaban dari saya-purist (di mana "saya" secara harfiah adalah saya, karena saya pikir pertanyaan ini tidak memiliki jawaban formal "benar" tunggal ):
Dalam bahasa dinamis seperti JS dengan begitu banyak kemungkinan untuk menggunakan tipe dasar tambalan, atau membuat jenis kustom menggunakan fitur seperti
Object.prototype.valueOf
tidak mungkin untuk mengetahui apakah suatu fungsi murni hanya dengan melihatnya, karena tergantung pada pemanggil apakah mereka ingin. untuk menghasilkan efek samping.Demo:
Jawaban saya-pragmatis:
Dari definisi dari wikipedia
Dengan kata lain, itu hanya masalah bagaimana suatu fungsi berperilaku, bukan bagaimana itu diterapkan. Dan selama fungsi tertentu menyimpan 2 properti ini - murni terlepas dari bagaimana tepatnya itu diterapkan.
Sekarang ke fungsi Anda:
Itu tidak murni karena tidak memenuhi syarat 2: itu tergantung pada IO secara transitif.Saya setuju pernyataan di atas salah, lihat jawaban lain untuk detail: https://stackoverflow.com/a/58749249/251311
Sumber daya relevan lainnya:
sumber
me
sebagai zerkms yang memberikan jawaban.add42
dan sayaaddX
adalah murni bahwa sayax
dapat diubah, dan Andaft
tidak dapat diubah (dan karenanya,add42
nilai pengembalian tidak bervariasi berdasarkan padaft
)?dollarToEuro
fungsi dalam contoh Anda tidak murni. Saya menjelaskan mengapa saya tidak setuju dengan jawaban saya. stackoverflow.com/a/58749249/783743Seperti jawaban lain katakan, cara Anda menerapkan
dollarToEuro
,memang murni, karena nilai tukar tidak diperbarui saat program sedang berjalan. Namun secara konseptual,
dollarToEuro
tampaknya itu harus menjadi fungsi yang tidak murni, karena menggunakan nilai tukar apa pun yang paling mutakhir. Cara paling sederhana untuk menjelaskan perbedaan ini adalah bahwa Anda belum menerapkandollarToEuro
tapidollarToEuroAtInstantOfProgramStart
.Kuncinya di sini adalah bahwa ada beberapa parameter yang diperlukan untuk menghitung konversi mata uang, dan bahwa versi jenderal yang benar-benar murni
dollarToEuro
akan memasok semuanya. Parameter paling langsung adalah jumlah USD untuk dikonversi, dan nilai tukar. Namun, karena Anda ingin mendapatkan nilai tukar dari informasi yang dipublikasikan, Anda sekarang memiliki tiga parameter untuk diberikan:Otoritas historis di sini adalah basis data Anda, dan dengan asumsi bahwa basis data tidak dikompromikan, akan selalu mengembalikan hasil yang sama untuk nilai tukar pada hari tertentu. Karenanya, dengan kombinasi ketiga parameter ini, Anda dapat menulis versi umum
dollarToEuro
yang sepenuhnya murni dan mandiri , yang mungkin terlihat seperti ini:Implementasi Anda menangkap nilai konstan untuk otoritas historis dan tanggal transaksi saat fungsi dibuat - otoritas historis adalah basis data Anda, dan tanggal yang diambil adalah tanggal saat Anda memulai program - yang tersisa hanyalah jumlah dolar , yang disediakan pemanggil. Versi tidak murni dari
dollarToEuro
yang selalu mendapatkan nilai paling terbaru pada dasarnya mengambil parameter tanggal secara implisit, mengaturnya ke instan fungsi dipanggil, yang tidak murni hanya karena Anda tidak pernah dapat memanggil fungsi dengan parameter yang sama dua kali.Jika Anda ingin memiliki versi murni
dollarToEuro
yang masih bisa mendapatkan nilai terbaru, Anda masih dapat mengikat otoritas historis, tetapi membiarkan parameter tanggal tidak terikat dan meminta tanggal dari penelepon sebagai argumen, berakhir dengan sesuatu seperti ini:sumber
Saya ingin sedikit mundur dari perincian spesifik JS dan abstraksi definisi formal, dan berbicara tentang kondisi mana yang perlu dipertahankan untuk memungkinkan optimalisasi spesifik. Itu biasanya hal utama yang kami pedulikan ketika menulis kode (meskipun itu juga membantu membuktikan kebenaran). Pemrograman fungsional bukan panduan untuk mode terbaru atau sumpah monastik penyangkalan diri. Ini adalah alat untuk menyelesaikan masalah.
Ketika Anda memiliki kode seperti ini:
Jika
exchangeRate
tidak pernah dapat dimodifikasi di antara dua panggilandollarToEuro(100)
, dimungkinkan untuk mem-memo hasil panggilan pertama kedollarToEuro(100)
dan mengoptimalkan panggilan kedua. Hasilnya akan sama, jadi kita bisa mengingat nilainya dari sebelumnya.The
exchangeRate
mungkin diatur sekali, sebelum memanggil fungsi apapun yang terlihat itu, dan tidak pernah dimodifikasi. Tidak terlalu membatasi, Anda mungkin memiliki kode yang mencariexchangeRate
sekali fungsi atau blok kode tertentu, dan menggunakan nilai tukar yang sama secara konsisten dalam lingkup itu. Atau, jika hanya utas ini yang dapat memodifikasi basis data, Anda akan berhak berasumsi bahwa, jika Anda tidak memperbarui nilai tukar, tidak ada orang lain yang mengubahnya pada Anda.Jika
fetchFromDatabase()
itu sendiri merupakan fungsi murni yang mengevaluasi ke sebuah konstanta, danexchangeRate
tidak dapat diubah, kita dapat melipat konstanta ini sepanjang jalan melalui perhitungan. Kompiler yang mengetahui hal ini sebagai kasusnya dapat membuat pengurangan yang sama yang Anda lakukan dalam komentar, yangdollarToEuro(100)
mengevaluasi ke 90.0, dan mengganti seluruh ekspresi dengan konstan 90.0.Namun, jika
fetchFromDatabase()
tidak melakukan I / O, yang dianggap sebagai efek samping, namanya melanggar Prinsip Least Astonishment.sumber
Fungsi ini tidak murni, itu bergantung pada variabel luar, yang hampir pasti akan berubah.
Karena itu fungsi gagal pada titik pertama yang Anda buat, tidak mengembalikan nilai yang sama ketika untuk argumen yang sama.
Untuk membuat fungsi ini "murni", berikan
exchangeRate
argumen.Ini kemudian akan memenuhi kedua kondisi tersebut.
Kode contoh:
sumber
const
.Untuk memperluas poin yang telah dibuat orang lain tentang transparansi referensial: kita dapat mendefinisikan kemurnian sebagai sekadar transparansi referensial panggilan fungsi (yaitu setiap panggilan ke fungsi dapat diganti dengan nilai pengembalian tanpa mengubah semantik program).
Dua properti yang Anda berikan adalah konsekuensi transparansi referensial. Misalnya, fungsi berikut
f1
tidak murni, karena tidak memberikan hasil yang sama setiap kali (properti yang Anda beri nomor 1):Mengapa penting untuk mendapatkan hasil yang sama setiap saat? Karena mendapatkan hasil yang berbeda adalah salah satu cara pemanggilan fungsi untuk memiliki semantik yang berbeda dari suatu nilai, dan karenanya memecah transparansi referensial.
Katakanlah kita menulis kode
f1("hello", "world")
, kita menjalankannya dan mendapatkan nilai balik"hello"
. Jika kami menemukan / mengganti setiap panggilanf1("hello", "world")
dan menggantinya dengan"hello"
kami, kami akan mengubah semantik program (semua panggilan sekarang akan diganti"hello"
, tetapi semula sekitar setengahnya akan dievaluasi"world"
). Karenanya panggilan untukf1
tidak transparan secara referensial, karenanyaf1
tidak murni.Cara lain bahwa panggilan fungsi dapat memiliki semantik berbeda dengan nilai adalah dengan mengeksekusi pernyataan. Sebagai contoh:
Nilai kembali
f2("bar")
akan selalu"bar"
, tetapi semantik nilai"bar"
berbeda dari panggilanf2("bar")
karena yang terakhir juga akan masuk ke konsol. Mengganti satu dengan yang lain akan mengubah semantik program, sehingga tidak transparan secara referensi, dan karenanyaf2
tidak murni.Apakah
dollarToEuro
fungsi Anda transparan secara referensi (dan karenanya murni) tergantung pada dua hal:exchangeRate
akan pernah berubah dalam 'ruang lingkup' ituTidak ada ruang lingkup "terbaik" untuk digunakan; biasanya kita akan berpikir tentang satu kali menjalankan program, atau masa hidup proyek. Sebagai analogi, bayangkan bahwa nilai fungsi setiap cache di-cache (seperti tabel memo dalam contoh yang diberikan oleh @ aadit-m-shah): kapan kita perlu menghapus cache, untuk memastikan bahwa nilai basi tidak akan mengganggu kita semantik?
Jika
exchangeRate
sedang menggunakanvar
maka itu bisa berubah antara setiap panggilan kedollarToEuro
; kita perlu menghapus hasil cache di antara setiap panggilan, sehingga tidak akan ada transparansi referensial untuk dibicarakan.Dengan menggunakan
const
kami memperluas 'lingkup' ke menjalankan program: itu akan aman untuk cache nilai kembalidollarToEuro
sampai program selesai. Kita bisa membayangkan menggunakan makro (dalam bahasa seperti Lisp) untuk mengganti panggilan fungsi dengan nilai kembali mereka. Jumlah kemurnian ini biasa untuk hal-hal seperti nilai konfigurasi, opsi baris perintah, atau ID unik. Jika kita membatasi diri kita untuk berpikir tentang satu kali menjalankan program maka kita mendapatkan sebagian besar manfaat dari kemurnian, tetapi kita harus berhati-hati di seluruh proses (misalnya menyimpan data ke file, kemudian memuatnya dalam menjalankan lain). Saya tidak akan menyebut fungsi seperti itu "murni" dalam pengertian abstrak (misalnya jika saya menulis definisi kamus), tetapi tidak memiliki masalah dengan memperlakukannya sebagai murni dalam konteks .Jika kita memperlakukan masa proyek sebagai 'ruang lingkup' kita, maka kita adalah "yang paling transparan referensial" dan karenanya "yang paling murni", bahkan dalam arti abstrak. Kita tidak perlu menghapus cache hipotetis kita. Kami bahkan bisa melakukan "caching" ini dengan langsung menulis ulang kode sumber pada disk, untuk mengganti panggilan dengan nilai baliknya. Ini bahkan akan bekerja di seluruh proyek, misalnya kita bisa membayangkan database online fungsi dan nilai kembalinya, di mana siapa pun dapat mencari panggilan fungsi dan (jika itu dalam DB) menggunakan nilai kembali yang disediakan oleh seseorang di sisi lain dari dunia yang menggunakan fungsi identik bertahun-tahun lalu pada proyek yang berbeda.
sumber
Seperti yang tertulis, itu adalah fungsi murni. Tidak menghasilkan efek samping. Fungsi ini memiliki satu parameter formal, tetapi memiliki dua input, dan akan selalu menampilkan nilai yang sama untuk dua input apa pun.
sumber
Seperti yang telah Anda catat, "itu mungkin memberi saya hasil yang berbeda besok" . Jika itu masalahnya, jawabannya akan tegas "tidak" . Ini terutama terjadi jika perilaku yang Anda maksudkan
dollarToEuro
telah ditafsirkan dengan benar sebagai:Namun, ada interpretasi yang berbeda, di mana itu akan dianggap murni:
dollarToEuro
langsung di atas murni.Dari perspektif rekayasa perangkat lunak, penting untuk menyatakan ketergantungan
dollarToEuro
pada fungsifetchFromDatabase
. Oleh karena itu, refactor definisidollarToEuro
sebagai berikut:Dengan hasil ini, mengingat premis yang
fetchFromDatabase
berfungsi memuaskan, maka dapat disimpulkan bahwa proyeksifetchFromDatabase
ondollarToEuro
harus memuaskan. Atau pernyataan "fetchFromDatabase
murni" menyiratkandollarToEuro
murni (karenafetchFromDatabase
merupakan dasar untukdollarToEuro
oleh faktor skalar darix
.Dari posting asli, saya bisa mengerti itu
fetchFromDatabase
adalah waktu fungsi. Mari kita tingkatkan upaya refactoring untuk membuat pemahaman itu transparan, karenanya jelas memenuhi syaratfetchFromDatabase
sebagai fungsi murni:fetchFromDatabase = (timestamp) => {/ * begini implementasinya * /};
Pada akhirnya, saya akan memperbaiki fitur sebagai berikut:
Akibatnya,
dollarToEuro
dapat diuji unit hanya dengan membuktikan bahwa ia memanggil dengan benarfetchFromDatabase
(atau turunannyaexchangeRate
).sumber
dollarToEuro
; Saya sudah menyebutkan di OP bahwa mungkin ada kasus penggunaan lain. Saya memilih dollarToEuro karena langsung membangkitkan apa yang saya coba lakukan, tetapi mungkin ada sesuatu yang kurang halus yang tergantung pada variabel bebas yang dapat berubah, tetapi tidak harus sebagai fungsi waktu. Dengan mengingat hal itu, saya menemukan bahwa refactor yang terpilih adalah yang lebih mudah diakses dan yang dapat membantu orang lain dengan kasus penggunaan yang serupa. Bagaimanapun, terima kasih atas bantuan Anda.Saya seorang bilingual Haskell / JS dan Haskell adalah salah satu bahasa yang membuat masalah besar tentang kemurnian fungsi, jadi saya pikir saya akan memberi Anda perspektif dari bagaimana Haskell melihatnya.
Seperti yang dikatakan orang lain, di Haskell, membaca variabel yang bisa berubah umumnya dianggap tidak murni. Ada perbedaan antara variabel dan definisi bahwa variabel dapat berubah nanti, definisi adalah sama selamanya. Jadi jika Anda telah mendeklarasikannya
const
(dengan asumsi itu hanya sebuahnumber
dan tidak memiliki struktur internal yang bisa berubah), membaca dari itu akan menggunakan definisi, yang murni. Tetapi Anda ingin memodelkan nilai tukar yang berubah dari waktu ke waktu, dan itu membutuhkan semacam ketidakstabilan dan kemudian Anda menjadi kotor.Untuk menggambarkan hal-hal tidak murni semacam itu (kita bisa menyebutnya "efek", dan penggunaannya "efektif" sebagai lawan "murni") di Haskell, kami melakukan apa yang Anda sebut metaprogramming . Hari ini metaprogramming biasanya mengacu pada makro yang bukan apa yang saya maksudkan, tetapi lebih kepada gagasan menulis suatu program untuk menulis program lain secara umum.
Dalam hal ini, di Haskell, kita menulis perhitungan murni yang menghitung program yang efektif yang kemudian akan melakukan apa yang kita inginkan. Jadi seluruh poin dari file sumber Haskell (setidaknya, yang menggambarkan sebuah program, bukan perpustakaan) adalah untuk menggambarkan perhitungan murni untuk program yang efektif yang menghasilkan-kekosongan, yang disebut
main
. Maka tugas kompiler Haskell adalah mengambil file sumber ini, melakukan perhitungan murni itu, dan meletakkan program yang efektif sebagai biner yang dapat dieksekusi di suatu tempat di hard drive Anda untuk dijalankan kemudian di waktu luang Anda. Ada celah, dengan kata lain, antara waktu ketika komputasi murni berjalan (sementara kompiler membuat executable) dan waktu ketika program yang efektif berjalan (setiap kali Anda menjalankan executable).Jadi bagi kami, program yang efektif adalah benar-benar struktur data dan mereka secara intrinsik tidak melakukan apa-apa hanya dengan disebutkan (mereka tidak memiliki efek * samping- * selain nilai pengembalian mereka; nilai pengembaliannya mengandung efeknya). Untuk contoh kelas TypeScript yang sangat ringan yang menjelaskan program yang tidak dapat diubah dan beberapa hal yang dapat Anda lakukan dengannya,
Kuncinya adalah bahwa jika Anda memiliki
Program<x>
efek samping maka tidak terjadi dan ini benar-benar murni entitas fungsional. Memetakan fungsi melalui program tidak memiliki efek samping apa pun kecuali fungsi itu bukan fungsi murni; mengurutkan dua program tidak memiliki efek samping; dll.Jadi misalnya bagaimana menerapkan ini dalam kasus Anda, Anda dapat menulis beberapa fungsi murni yang mengembalikan program untuk mendapatkan pengguna dengan ID dan untuk mengubah database dan mengambil data JSON, seperti
dan kemudian Anda bisa mendeskripsikan pekerjaan cron untuk meringkuk URL dan mencari karyawan dan memberi tahu atasan mereka dengan cara yang berfungsi murni sebagai
Intinya adalah bahwa setiap fungsi di sini adalah fungsi yang sepenuhnya murni; tidak ada yang benar-benar terjadi sampai saya benar
action.run()
- benar menggerakkannya. Selain itu saya dapat menulis fungsi seperti,dan jika JS memiliki janji pembatalan kami dapat memiliki dua program saling berlomba dan mengambil hasil pertama dan membatalkan yang kedua. (Maksudku, kita masih bisa, tetapi menjadi kurang jelas apa yang harus dilakukan.)
Demikian pula dalam kasus Anda, kami dapat menjelaskan perubahan nilai tukar
dan
exchangeRate
bisa menjadi program yang melihat nilai yang bisa berubah,tetapi meskipun demikian, fungsi
dollarsToEuros
ini sekarang merupakan fungsi murni dari angka ke program yang menghasilkan angka, dan Anda dapat mempertimbangkannya dengan cara yang sama dan deterministik sehingga Anda dapat mempertimbangkan tentang program apa pun yang tidak memiliki efek samping.Biaya, tentu saja, adalah bahwa Anda harus akhirnya memanggilnya di
.run()
suatu tempat , dan itu tidak murni. Tetapi seluruh struktur perhitungan Anda dapat dijelaskan dengan perhitungan murni, dan Anda dapat mendorong pengotor ke margin kode Anda.sumber