Saya harus mengakui bahwa saya tidak tahu banyak tentang pemrograman fungsional. Saya membacanya dari sana-sini, dan jadi tahu bahwa dalam pemrograman fungsional, fungsi mengembalikan output yang sama, untuk input yang sama, tidak peduli berapa kali fungsi dipanggil. Persis seperti fungsi matematika yang mengevaluasi ke output yang sama untuk nilai yang sama dari parameter input yang terlibat dalam ekspresi fungsi.
Sebagai contoh, pertimbangkan ini:
f(x,y) = x*x + y; // It is a mathematical function
Tidak peduli berapa kali Anda menggunakan f(10,4)
, nilainya akan selalu 104
. Dengan demikian, di mana pun Anda menulis f(10,4)
, Anda dapat menggantinya dengan 104
, tanpa mengubah nilai keseluruhan ekspresi. Properti ini disebut sebagai transparansi referensial ekspresi.
Seperti kata Wikipedia ( tautan ),
Sebaliknya, dalam kode fungsional, nilai output suatu fungsi hanya bergantung pada argumen yang merupakan input ke fungsi, sehingga memanggil fungsi f dua kali dengan nilai yang sama untuk argumen x akan menghasilkan hasil yang sama f (x) pada kedua kali.
Bisakah fungsi waktu (yang mengembalikan waktu saat ini ) ada dalam pemrograman fungsional?
Jika ya, lalu bagaimana itu bisa ada? Apakah itu tidak melanggar prinsip pemrograman fungsional? Ini khususnya melanggar transparansi referensial yang merupakan salah satu properti pemrograman fungsional (jika saya memahaminya dengan benar).
Atau jika tidak, lalu bagaimana orang bisa tahu waktu saat ini dalam pemrograman fungsional?
Jawaban:
Cara lain untuk menjelaskannya adalah ini: tidak ada fungsi yang bisa mendapatkan waktu saat ini (karena terus berubah), tetapi suatu tindakan bisa mendapatkan waktu saat ini. Katakanlah itu
getClockTime
adalah fungsi konstan (atau fungsi nullary, jika Anda suka) yang mewakili tindakan mendapatkan waktu saat ini. Ini tindakan adalah sama setiap kali tidak peduli bila digunakan sehingga merupakan konstan nyata.Demikian juga, katakanlah
print
adalah fungsi yang membutuhkan representasi waktu dan mencetaknya ke konsol. Karena panggilan fungsi tidak dapat memiliki efek samping dalam bahasa fungsional murni, kami malah membayangkan bahwa itu adalah fungsi yang mengambil stempel waktu dan mengembalikan tindakan mencetaknya ke konsol. Sekali lagi, ini adalah fungsi nyata, karena jika Anda memberikan stempel waktu yang sama, itu akan mengembalikan tindakan yang sama dengan mencetaknya setiap waktu.Sekarang, bagaimana Anda bisa mencetak waktu saat ini ke konsol? Nah, Anda harus menggabungkan dua tindakan. Jadi bagaimana kita bisa melakukan itu? Kita tidak bisa hanya lewat
getClockTime
untukprint
, karena cetak mengharapkan timestamp, bukan tindakan. Tetapi kita dapat membayangkan bahwa ada operator,>>=
yang menggabungkan dua tindakan, yang mendapat cap waktu, dan yang mengambil satu sebagai argumen dan mencetaknya. Menerapkan ini ke tindakan yang disebutkan sebelumnya, hasilnya adalah ... tadaaa ... tindakan baru yang mendapatkan waktu saat ini dan mencetaknya. Dan ini kebetulan persis bagaimana hal itu dilakukan di Haskell.Jadi, secara konseptual, Anda dapat melihatnya dengan cara ini: Program fungsional murni tidak melakukan I / O, itu mendefinisikan tindakan , yang kemudian dijalankan oleh sistem runtime. The tindakan yang sama setiap waktu, tapi hasil mengeksekusi itu tergantung pada keadaan ketika dieksekusi.
Saya tidak tahu apakah ini lebih jelas daripada penjelasan lainnya, tetapi kadang-kadang membantu saya untuk berpikir seperti ini.
sumber
getClockTime
tindakan alih-alih fungsi. Nah, jika Anda memanggilnya, maka panggil setiap tindakan fungsi , maka bahkan pemrograman imperatif akan menjadi pemrograman fungsional. Atau mungkin, Anda ingin menyebutnya pemrograman aksial .main
tindakan Anda . Ini memungkinkan kode fungsional murni dipisahkan dari kode imperatif, dan pemisahan ini diberlakukan oleh sistem tipe. Memperlakukan aksi sebagai objek kelas satu juga memungkinkan Anda untuk menyebarkannya dan membangun "struktur kontrol" Anda sendiri.->
- begitulah standar mendefinisikan istilah dan itulah satu-satunya definisi yang masuk akal dalam konteks Haskell. Jadi sesuatu yang jenisIO Whatever
ini tidak fungsi.putStrLn
bukan tindakan - itu adalah fungsi yang mengembalikan tindakan.getLine
adalah variabel yang berisi tindakan. Tindakan adalah nilai, variabel dan fungsi adalah "wadah" / "label" yang kami berikan pada tindakan itu.Iya dan tidak.
Bahasa pemrograman fungsional yang berbeda menyelesaikannya secara berbeda.
Dalam Haskell (yang sangat murni) semua hal ini harus terjadi dalam sesuatu yang disebut I / O Monad - lihat di sini .
Anda dapat menganggapnya sebagai mendapatkan input lain (dan output) ke fungsi Anda (negara-dunia) atau lebih mudah sebagai tempat di mana "kenajisan" seperti mendapatkan waktu yang berubah terjadi.
Bahasa lain seperti F # hanya memiliki beberapa ketidakmurnian bawaan sehingga Anda dapat memiliki fungsi yang mengembalikan nilai yang berbeda untuk input yang sama - seperti bahasa imperatif normal .
Seperti yang Jeffrey Burka katakan dalam komentarnya: Ini adalah pengantar bagus untuk I / O Monad langsung dari wiki Haskell.
sumber
Dalam Haskell kita menggunakan konstruksi yang disebut monad untuk menangani efek samping. Monad pada dasarnya berarti bahwa Anda merangkum nilai ke dalam wadah dan memiliki beberapa fungsi untuk mengaitkan fungsi dari nilai ke nilai di dalam wadah. Jika wadah kami memiliki jenis:
kita dapat dengan aman menerapkan tindakan IO. Tipe ini berarti: Tindakan tipe
IO
adalah fungsi, yang mengambil token tipeRealWorld
dan mengembalikan token baru, bersama dengan hasilnya.Gagasan di balik ini adalah bahwa setiap tindakan IO mengubah keadaan luar, diwakili oleh token ajaib
RealWorld
. Dengan menggunakan monads, seseorang dapat menghubungkan berbagai fungsi yang bermutasi bersama di dunia nyata. Fungsi paling penting dari monad adalah>>=
, diikat mengikat :>>=
mengambil satu tindakan dan fungsi yang mengambil hasil dari tindakan ini dan membuat tindakan baru dari ini. Jenis kembali adalah tindakan baru. Misalnya, mari kita berpura-pura ada fungsinow :: IO String
, yang mengembalikan sebuah String yang mewakili waktu saat ini. Kita dapat menghubungkannya dengan fungsiputStrLn
untuk mencetaknya:Atau ditulis dalam
do
-Notasi, yang lebih dikenal oleh programmer imperatif:Semua ini murni, karena kami memetakan mutasi dan informasi tentang dunia di luar
RealWorld
token. Jadi setiap kali Anda menjalankan tindakan ini, tentu saja Anda mendapatkan output yang berbeda, tetapi inputnya tidak sama:RealWorld
tokennya berbeda.sumber
RealWorld
layar asap. Namun, yang paling penting adalah bagaimana benda yang diklaim ini diteruskan dalam sebuah rantai. Bagian yang hilang adalah tempat dimulainya, di mana sumber atau koneksi ke dunia nyata adalah - dimulai dengan fungsi utama yang berjalan di monad IO.RealWorld
objek global yang dilewatkan ke dalam program ketika dimulai.main
fungsi Anda membutuhkanRealWorld
argumen. Hanya setelah eksekusi itu diteruskan.RealWorld
dan hanya menyediakan fungsi kecil untuk mengubahnya sepertiputStrLn
, adalah karena beberapa programmer Haskell tidak berubahRealWorld
dengan salah satu program mereka sehingga alamat dan tanggal lahir Haskell Curry sedemikian rupa sehingga mereka menjadi tetangga sebelah. tumbuh dewasa (ini dapat merusak kontinum ruang-waktu sedemikian rupa hingga melukai bahasa pemrograman Haskell.)RealWorld -> (a, RealWorld)
tidak memecah sebagai metafora bahkan di bawah konkurensi, selama Anda ingat bahwa dunia nyata mungkin diubah oleh bagian lain dari alam semesta di luar fungsi Anda (atau proses Anda saat ini) setiap saat. Jadi (a) metafora tidak rusak, dan (b) setiap kali nilai yang memilikiRealWorld
jenisnya diteruskan ke suatu fungsi, fungsi tersebut harus dievaluasi kembali, karena dunia nyata akan berubah sementara itu ( yang dimodelkan sebagai @fuz menjelaskan, memberikan kembali 'nilai token' yang berbeda setiap kali kita berinteraksi dengan dunia nyata).Sebagian besar bahasa pemrograman fungsional tidak murni, yaitu mereka memungkinkan fungsi tidak hanya bergantung pada nilai-nilai mereka. Dalam bahasa-bahasa itu sangat mungkin untuk memiliki fungsi mengembalikan waktu saat ini. Dari bahasa yang Anda beri tag pertanyaan ini berlaku untuk Scala dan F # (serta sebagian besar varian ML lainnya ).
Dalam bahasa seperti Haskell dan Clean , yang murni, situasinya berbeda. Dalam Haskell waktu saat ini tidak akan tersedia melalui fungsi, tetapi apa yang disebut tindakan IO, yang merupakan cara Haskell untuk mengenkapsulasi efek samping.
Dalam Bersihkan itu akan menjadi fungsi, tetapi fungsi akan mengambil nilai dunia sebagai argumennya dan mengembalikan nilai dunia yang baru (selain waktu saat ini) sebagai hasilnya. Sistem tipe akan memastikan bahwa setiap nilai dunia dapat digunakan hanya sekali (dan setiap fungsi yang mengkonsumsi nilai dunia akan menghasilkan yang baru). Dengan cara ini fungsi waktu harus dipanggil dengan argumen yang berbeda setiap kali dan dengan demikian akan diizinkan untuk mengembalikan waktu yang berbeda setiap kali.
sumber
"Waktu sekarang" bukan fungsi. Itu adalah parameter. Jika kode Anda tergantung pada waktu sekarang, itu berarti kode Anda diparameterisasi berdasarkan waktu.
sumber
Ini benar-benar dapat dilakukan dengan cara yang murni fungsional. Ada beberapa cara untuk melakukannya, tetapi yang paling sederhana adalah mengembalikan fungsi waktu tidak hanya waktu tetapi juga fungsi yang harus Anda panggil untuk mendapatkan pengukuran waktu berikutnya .
Di C # Anda bisa menerapkannya seperti ini:
(Perlu diingat bahwa ini adalah contoh yang dimaksudkan untuk menjadi sederhana, tidak praktis. Secara khusus, daftar node tidak dapat berupa sampah yang dikumpulkan karena mereka di-root oleh ProgramStartTime.)
Kelas 'ClockStamp' ini bertindak seperti daftar tertaut yang tidak dapat diubah, tetapi sebenarnya node dihasilkan sesuai permintaan sehingga dapat berisi waktu 'saat ini'. Setiap fungsi yang ingin mengukur waktu harus memiliki parameter 'clockStamp' dan juga harus mengembalikan pengukuran waktu terakhir dalam hasilnya (sehingga penelepon tidak melihat pengukuran lama), seperti ini:
Tentu saja, agak merepotkan harus melewati pengukuran terakhir masuk dan keluar, masuk dan keluar, masuk dan keluar. Ada banyak cara untuk menyembunyikan pelat tungku, terutama pada tingkat desain bahasa. Saya pikir Haskell menggunakan trik semacam ini dan kemudian menyembunyikan bagian yang jelek dengan menggunakan monads.
sumber
i++
di dalam for loop tidak transparan transparan;)struct TimeKleisli<Arg, Res> { private delegate Res(TimeStampedValue<Arg>); }
. Tetapi kode dengan ini masih tidak akan sebagus Haskell dengando
sintaksis.SelectMany
, yang memungkinkan sintaks pemahaman kueri. Anda masih tidak dapat memprogram secara polimorfik di atas monad, jadi semua ini merupakan perjuangan berat melawan sistem tipe lemah :(Saya terkejut bahwa tidak ada jawaban atau komentar yang menyebutkan coalgebras atau coinduction. Biasanya, coinduction disebutkan ketika beralasan tentang struktur data yang tak terbatas, tetapi juga berlaku untuk aliran pengamatan tanpa akhir, seperti register waktu pada CPU. Sebuah model coalgebra menyembunyikan keadaan; dan model coinduction mengamati keadaan itu. (Model induksi normal membangun kondisi.)
Ini adalah topik hangat dalam Pemrograman Fungsional Reaktif. Jika Anda tertarik pada hal-hal semacam ini, baca ini: http://digitalcommons.ohsu.edu/csetech/91/ (28 hal.)
sumber
Ya, mungkin fungsi murni mengembalikan waktu, jika diberikan waktu itu sebagai parameter. Argumen waktu berbeda, hasil waktu berbeda. Kemudian bentuklah fungsi waktu lainnya juga dan gabungkan dengan kosakata fungsi sederhana (-waktu) -transformasi (orde tinggi). Karena pendekatan ini tidak memiliki kewarganegaraan, waktu di sini dapat berkelanjutan (resolusi-independen) daripada diskrit, sangat meningkatkan modularitas . Intuisi ini adalah dasar dari Pemrograman Fungsional Reaktif (FRP).
sumber
Iya! Anda benar! Now () atau CurrentTime () atau metode tanda tangan apa pun dari cita rasa tersebut tidak menunjukkan transparansi referensial dalam satu cara. Tetapi dengan instruksi ke kompiler itu diparameterisasi oleh input jam sistem.
Dengan output, Now () mungkin terlihat tidak mengikuti transparansi referensial. Tetapi perilaku aktual dari jam sistem dan fungsi di atasnya mematuhi transparansi referensial.
sumber
Ya, fungsi mendapatkan waktu dapat ada dalam pemrograman fungsional menggunakan versi yang sedikit dimodifikasi pada pemrograman fungsional yang dikenal sebagai pemrograman fungsional tidak murni (default atau yang utama adalah pemrograman fungsional murni).
Dalam hal mendapatkan waktu (atau membaca file, atau meluncurkan rudal) kode perlu berinteraksi dengan dunia luar untuk menyelesaikan pekerjaan dan dunia luar ini tidak didasarkan pada dasar murni pemrograman fungsional. Untuk memungkinkan dunia pemrograman fungsional murni untuk berinteraksi dengan dunia luar tidak murni ini, orang-orang telah memperkenalkan pemrograman fungsional tidak murni. Bagaimanapun, perangkat lunak yang tidak berinteraksi dengan dunia luar tidak ada gunanya selain melakukan beberapa perhitungan matematis.
Beberapa bahasa pemrograman pemrograman fungsional memiliki fitur pengotor ini bawaan di dalamnya sehingga tidak mudah untuk memisahkan kode mana yang tidak murni dan mana yang murni (seperti F #, dll.) Dan beberapa bahasa pemrograman fungsional memastikan bahwa ketika Anda melakukan beberapa hal yang tidak murni kode itu jelas menonjol dibandingkan dengan kode murni, seperti Haskell.
Cara lain yang menarik untuk melihat ini adalah bahwa fungsi waktu Anda dalam pemrograman fungsional akan mengambil objek "dunia" yang memiliki keadaan dunia saat ini seperti waktu, jumlah orang yang hidup di dunia, dll. Lalu dapatkan waktu dari dunia mana objek akan selalu murni yaitu Anda lulus dalam keadaan dunia yang sama Anda akan selalu mendapatkan waktu yang sama.
sumber
Pertanyaan Anda mengonfigurasi dua ukuran terkait bahasa komputer: fungsional / imperatif dan murni / tidak murni.
Bahasa fungsional mendefinisikan hubungan antara input dan output fungsi, dan bahasa imperatif menjelaskan operasi spesifik dalam urutan tertentu untuk dilakukan.
Bahasa murni tidak membuat atau bergantung pada efek samping, dan bahasa yang tidak murni menggunakannya di seluruh.
Seratus persen program murni pada dasarnya tidak berguna. Mereka mungkin melakukan perhitungan yang menarik, tetapi karena mereka tidak dapat memiliki efek samping mereka tidak memiliki input atau output sehingga Anda tidak akan pernah tahu apa yang mereka hitung.
Agar bermanfaat sama sekali, suatu program harus setidaknya tidak murni. Salah satu cara untuk membuat program murni bermanfaat adalah dengan meletakkannya di dalam pembungkus tipis yang tidak murni. Seperti program Haskell yang belum teruji ini:
sumber
IO
nilai dan hasil murni.Anda sedang membicarakan topik yang sangat penting dalam pemrograman fungsional, yaitu melakukan I / O. Cara banyak bahasa murni mengatasinya adalah dengan menggunakan bahasa khusus domain yang disematkan, misalnya, subbahasa yang tugasnya menyandikan tindakan , yang dapat memberikan hasil.
Runtime Haskell misalnya mengharapkan saya untuk mendefinisikan tindakan yang disebut
main
yang terdiri dari semua tindakan yang membentuk program saya. Runtime kemudian menjalankan tindakan ini. Sebagian besar waktu, dalam melakukan itu mengeksekusi kode murni. Dari waktu ke waktu runtime akan menggunakan data yang dikomputasi untuk melakukan I / O dan mengembalikan data kembali ke kode murni.Anda mungkin mengeluh bahwa ini kedengarannya seperti curang, dan memang demikian: dengan mendefinisikan tindakan dan mengharapkan runtime untuk mengeksekusinya, pemrogram dapat melakukan semua yang dapat dilakukan oleh program normal. Tetapi sistem tipe Haskell yang kuat menciptakan penghalang yang kuat antara bagian murni dan "tidak murni" dari program: Anda tidak bisa hanya menambahkan, katakanlah, dua detik ke waktu CPU saat ini, dan mencetaknya, Anda harus menentukan tindakan yang menghasilkan saat ini Waktu CPU, dan meneruskan hasilnya ke tindakan lain yang menambahkan dua detik dan mencetak hasilnya. Menulis terlalu banyak program dianggap sebagai gaya yang buruk, karena itu membuatnya sulit untuk menyimpulkan efek yang ditimbulkan, dibandingkan dengan jenis Haskell yang memberi tahu kita semua yang dapat kita ketahui tentang nilai.
Contoh:
clock_t c = time(NULL); printf("%d\n", c + 2);
dalam C, vs.main = getCPUTime >>= \c -> print (c + 2*1000*1000*1000*1000)
di Haskell. Operator>>=
digunakan untuk menyusun tindakan, meneruskan hasil dari yang pertama ke fungsi yang menghasilkan tindakan kedua. Ini terlihat sangat misterius, kompiler Haskell mendukung sintaksis gula yang memungkinkan kita untuk menulis kode yang terakhir sebagai berikut:Yang terakhir terlihat cukup penting, bukan?
sumber
Itu tidak ada dalam arti fungsional murni.
Pertama-tama mungkin berguna untuk mengetahui bagaimana suatu waktu diambil pada komputer. Pada dasarnya ada sirkuit onboard yang melacak waktu (yang merupakan alasan komputer biasanya membutuhkan baterai sel kecil). Kemudian mungkin ada beberapa proses internal yang menetapkan nilai waktu pada register memori tertentu. Yang pada dasarnya bermuara pada nilai yang dapat diambil oleh CPU.
Untuk Haskell, ada konsep 'tindakan IO' yang mewakili jenis yang dapat dibuat untuk melakukan beberapa proses IO. Jadi alih-alih mereferensikan
time
nilai, kami mereferensikanIO Time
nilai. Semua ini akan berfungsi murni. Kami tidak mereferensikantime
tetapi sesuatu di sepanjang baris 'baca nilai register waktu' .Ketika kita benar-benar menjalankan program Haskell, tindakan IO sebenarnya akan terjadi.
sumber