Apakah semua bahasa fungsional menggunakan pengumpulan sampah?

32

Apakah ada bahasa fungsional yang memungkinkan untuk menggunakan semantik stack - penghancuran deterministik otomatis di akhir ruang lingkup?

pengguna467799
sumber
Penghancuran deterministik benar-benar hanya membantu dengan efek samping. Dalam konteks pemrograman fungsional murni , itu hanya berarti memastikan bahwa tindakan tertentu (monadik) selalu berjalan di akhir urutan. Selain itu, mudah untuk menulis bahasa gabungan fungsional yang tidak memerlukan pengumpulan sampah.
Jon Purdy
Saya tertarik dengan pertanyaannya, apa hubungannya dengan otehr?
mattnz
1
Dalam bahasa fungsional tanpa pengumpulan sampah, saya tidak melihat bagaimana berbagi struktural dari struktur data yang tidak dapat diubah dimungkinkan. Dimungkinkan untuk membuat bahasa seperti itu, tetapi itu bukan bahasa yang saya gunakan.
dan_waterworth
Karat memiliki banyak fitur yang biasanya diidentifikasi sebagai 'fungsional' (setidaknya, mereka biasanya dicari di non-func langs sebagai fitur fungsional). Saya ingin tahu apa yang hilang. Immut secara default, penutupan, lewat fungsi, overload berprinsip, ADT (belum ada GADT), pencocokan pola, semua tanpa GC. Apa lagi?
Noein

Jawaban:

10

Bukan yang saya tahu, meskipun saya bukan ahli pemrograman fungsional.

Tampaknya pada prinsipnya cukup sulit, karena nilai yang dikembalikan dari fungsi dapat berisi referensi ke nilai lain yang dibuat (di stack) dalam fungsi yang sama, atau mungkin dengan mudah diteruskan sebagai parameter, atau direferensikan oleh sesuatu yang diteruskan ke dalam sebagai parameter. Dalam C, masalah ini ditangani dengan memungkinkan bahwa pointer menggantung (atau lebih tepatnya, perilaku tidak terdefinisi) dapat terjadi jika programmer tidak melakukan sesuatu dengan benar. Itu bukan jenis solusi yang disetujui oleh desainer bahasa fungsional.

Ada beberapa solusi potensial. Satu ide adalah menjadikan nilai seumur hidup sebagai bagian dari jenis nilai, bersama dengan referensi untuknya, dan menetapkan aturan berbasis tipe yang mencegah nilai yang dialokasikan stack dikembalikan, atau direferensikan oleh sesuatu yang dikembalikan dari, suatu fungsi. Saya belum menyelesaikan implikasinya, tetapi saya curiga ini akan mengerikan.

Untuk kode monadik, ada solusi lain yang (sebenarnya atau hampir) monadik juga, dan dapat memberikan semacam IORef yang hancur secara deterministik secara otomatis. Prinsipnya adalah mendefinisikan tindakan "bersarang". Ketika digabungkan (menggunakan operator asosiatif), ini menentukan aliran kontrol bersarang - saya pikir "elemen XML", dengan nilai paling kiri memberikan pasangan tag awal dan akhir. "Tag XML" ini hanya mendefinisikan urutan tindakan monadik pada tingkat abstraksi lain.

Pada titik tertentu (di sisi kanan rantai komposisi asosiatif) Anda memerlukan semacam terminator untuk mengakhiri sarang - sesuatu untuk mengisi lubang di tengah. Kebutuhan akan terminator adalah apa yang mungkin berarti operator komposisi bersarang tidak monadik, sekali lagi, saya tidak sepenuhnya yakin karena saya belum mengerjakan detailnya. Seperti yang diterapkan semua terminator adalah mengubah tindakan bersarang menjadi efektif tindakan monadik normal, mungkin tidak - itu tidak selalu mempengaruhi operator komposisi bersarang.

Banyak dari tindakan khusus ini akan memiliki langkah "tag-akhir" nol, dan akan menyamakan langkah "tag awal" dengan beberapa tindakan monadik sederhana. Tetapi beberapa akan mewakili deklarasi variabel. Ini akan mewakili konstruktor dengan tag-awal, dan destruktor dengan tag-akhir. Jadi Anda mendapatkan sesuatu seperti ...

act = terminate ((def-var "hello" ) >>>= \h ->
                 (def-var " world") >>>= \w ->
                 (use-val ((get h) ++ (get w)))
                )

Menerjemahkan ke komposisi monadik dengan urutan eksekusi berikut, setiap tag (bukan elemen) menjadi tindakan monadik yang normal ...

<def-var val="hello">  --  construction
  <def-var val=" world>  --  construction
    <use-val ...>
      <terminator/>
    </use-val>  --  do nothing
  </def-val>  --  destruction
</def-val>  --  destruction

Aturan seperti ini dapat memungkinkan C ++ - style RAII diimplementasikan. Referensi seperti IORef tidak dapat lepas dari jangkauannya, karena alasan yang sama dengan alasan mengapa IORef normal tidak dapat lepas dari monad - aturan komposisi asosiatif tidak memberikan jalan bagi referensi untuk melarikan diri.

Suntingan - Saya hampir lupa mengatakan - ada area yang pasti saya tidak yakin di sini. Sangat penting untuk memastikan bahwa variabel luar tidak bisa mereferensikan variabel dalam, jadi harus ada batasan yang dapat Anda lakukan dengan referensi seperti IORef ini. Sekali lagi, saya belum mengerjakan semua detailnya.

Oleh karena itu, konstruksi dapat mis membuka file yang menutup kerusakan. Konstruksi dapat membuka soket yang menutup kerusakan. Pada dasarnya, seperti dalam C ++, variabel menjadi manajer sumber daya. Tetapi tidak seperti C ++, tidak ada objek yang dialokasikan heap yang tidak dapat secara otomatis dihancurkan.

Meskipun struktur ini mendukung RAII, Anda masih memerlukan pengumpul sampah. Meskipun tindakan bersarang dapat mengalokasikan dan membebaskan memori, memperlakukannya sebagai sumber daya, masih ada semua referensi nilai fungsional (berpotensi dibagi) di dalam memori tersebut dan di tempat lain. Mengingat bahwa memori dapat dengan mudah dialokasikan pada tumpukan, menghindari kebutuhan akan tumpukan bebas, arti sebenarnya (jika ada) adalah untuk jenis manajemen sumber daya lainnya.

Jadi yang dicapai adalah untuk memisahkan manajemen sumber daya gaya RAII dari manajemen memori, setidaknya dalam kasus di mana RAII didasarkan pada ruang lingkup bersarang yang sederhana. Anda masih memerlukan pengumpul sampah untuk manajemen memori, tetapi Anda mendapatkan pembersihan deterministik otomatis yang aman dan tepat waktu dari sumber daya lainnya.

Steve314
sumber
Saya gagal melihat mengapa GC diperlukan dalam semua bahasa fungsional. jika Anda memiliki kerangka kerja RAII gaya C ++ di tempatnya, kompiler dapat menggunakan mekanisme itu juga. Nilai yang dibagikan tidak ada masalah untuk kerangka kerja RAII (lihat C ++ shared_ptr<>), Anda masih menyimpan kehancuran deterministik. Satu hal yang rumit untuk RAII adalah referensi siklik; RAII berfungsi dengan baik jika grafik kepemilikannya adalah Grafik Acyclic yang Diarahkan.
MSalters
Masalahnya, gaya pemrograman fungsional praktis dibangun di sekitar fungsi closures / lambdas / anonim. Tanpa GC, Anda tidak memiliki kebebasan yang sama untuk menggunakan penutupan Anda, sehingga bahasa Anda menjadi kurang berfungsi secara signifikan.
Datang badai
@comingstorm - C ++ memiliki lambdas (per C ++ 11) tetapi tidak ada pengumpul sampah standar. Lambda membawa lingkungan mereka dalam penutupan juga - dan elemen-elemen dalam lingkungan itu dapat dilewati dengan referensi, serta kemungkinan pointer diteruskan oleh nilai. Tetapi seperti yang saya tulis di paragraf kedua saya, C ++ memungkinkan kemungkinan menggantung pointer - itu adalah programmer (daripada kompiler atau lingkungan runtime) tanggung jawab untuk memastikan bahwa tidak pernah terjadi.
Steve314
@MSalters - ada biaya yang diperlukan untuk memastikan tidak ada siklus referensi yang dapat dibuat. Oleh karena itu setidaknya tidak sepele untuk membuat bahasa bertanggung jawab atas pembatasan itu. Menugaskan penunjuk mungkin menjadi operasi yang tidak konstan. Meskipun mungkin masih menjadi pilihan terbaik dalam beberapa kasus. Pengumpulan sampah menghindari masalah itu, dengan biaya yang berbeda. Membuat programmer bertanggung jawab adalah hal lain. Tidak ada alasan kuat mengapa pointer menggantung harus OK dalam bahasa imperatif tetapi tidak fungsional, tapi saya masih tidak merekomendasikan menulis pointer-dangling-Haskell.
Steve314
Saya berpendapat bahwa manajemen memori manual berarti Anda tidak memiliki kebebasan yang sama untuk menggunakan penutupan C ++ 11 seperti yang Anda lakukan pada penutupan Lisp atau Haskell. (Saya sebenarnya cukup tertarik untuk memahami detail dari tradeoff ini, karena saya ingin menulis bahasa pemrograman sistem fungsional ...)
comingstorm
3

Jika Anda menganggap C ++ sebagai bahasa fungsional (memiliki lambdas), maka itu adalah contoh bahasa yang tidak menggunakan pengumpulan sampah.

BЈовић
sumber
8
Bagaimana jika Anda tidak menganggap C ++ sebagai bahasa fungsional (IMHO tidak, meskipun Anda dapat menulis program fungsional dengan itu, Anda juga dapat menulis beberapa program yang sangat non-fucntional (berfungsi ....) dengan itu)
mattnz
@ mattnz Maka saya kira jawabannya tidak berlaku. Saya tidak yakin apa yang terjadi dalam bahasa lain (seperti misalnya haskel)
BЈовић
9
Mengatakan C ++ fungsional seperti mengatakan bahwa Perl berorientasi pada objek ...
Dynamic
Setidaknya kompiler c ++ dapat memeriksa efek samping. (via const)
tp1
@ tp1 - (1) Saya harap ini tidak akan mundur ke bahasa siapa yang terbaik, dan (2) itu tidak benar. Pertama, efek yang sangat penting kebanyakan adalah I / O. Kedua, bahkan untuk efek pada memori yang bisa berubah, const tidak memblokirnya. Bahkan jika Anda berasumsi tidak ada kemungkinan untuk menumbangkan sistem huruf (umumnya masuk akal dalam C ++), ada masalah ketegasan logis dan kata kunci C ++ yang "bisa berubah". Pada dasarnya, Anda masih dapat memiliki mutasi meskipun const. Anda diharapkan untuk memastikan bahwa hasilnya masih "secara logis" sama, tetapi tidak selalu sama.
Steve314
2

Saya harus mengatakan bahwa pertanyaannya agak tidak jelas karena mengasumsikan bahwa ada kumpulan standar "bahasa fungsional". Hampir setiap bahasa pemrograman mendukung sejumlah pemrograman fungsional. Dan, hampir setiap bahasa pemrograman mendukung sejumlah pemrograman imperatif. Di mana orang menarik garis untuk mengatakan yang merupakan bahasa fungsional dan yang merupakan bahasa imperatif, selain dipandu oleh prasangka budaya dan dogma populer?

Cara yang lebih baik untuk mengutarakan pertanyaannya adalah, "apakah mungkin untuk mendukung pemrograman fungsional dalam memori yang dialokasikan dengan tumpukan". Jawabannya, seperti yang telah disebutkan, sangat sulit. Gaya pemrograman fungsional mempromosikan alokasi struktur data rekursif sesuai keinginan, yang membutuhkan memori tumpukan (apakah sampah dikumpulkan atau referensi dihitung). Namun, ada teknik analisis kompiler yang cukup canggih yang disebut analisis memori berbasis wilayah dimana kompiler dapat membagi tumpukan menjadi blok besar yang dapat dialokasikan dan didelokasi secara otomatis, dengan cara yang mirip dengan alokasi stack. Halaman Wikipedia mencantumkan berbagai implementasi teknik, untuk bahasa "fungsional" dan "imperatif".

Uday Reddy
sumber
1
Penghitungan referensi IMO adalah pengumpulan sampah, dan memiliki tumpukan tidak berarti bahwa itu adalah satu-satunya pilihan. C mallocs dan mfrees menggunakan heap, tetapi tidak memiliki pengumpul sampah (standar) dan hanya penghitungan referensi jika Anda menulis kode untuk melakukan itu. C ++ hampir sama - ia memiliki (sebagai standar, dalam C ++ 11) smart pointer dengan penghitungan referensi bawaan, tetapi Anda masih dapat melakukan manual baru dan menghapus jika Anda benar-benar perlu.
Steve314
Alasan umum untuk mengklaim bahwa penghitungan referensi bukan pengumpulan sampah adalah karena gagal mengumpulkan siklus referensi. Itu tentu berlaku untuk implementasi sederhana (mungkin termasuk C ++ smart pointer - saya belum memeriksa) tetapi tidak selalu demikian. Setidaknya satu mesin virtual Java (oleh IBM, IIRC) menggunakan penghitungan referensi sebagai dasar untuk pengumpulan sampahnya.
Steve314