Seberapa sering seq digunakan dalam kode produksi Haskell?

23

Saya memiliki pengalaman menulis alat kecil di Haskell dan saya merasa sangat intuitif untuk digunakan, terutama untuk menulis filter (menggunakan interact) yang memproses input standar mereka dan menyalurkannya ke output standar.

Baru-baru ini saya mencoba menggunakan satu filter seperti itu pada file yang sekitar 10 kali lebih besar dari biasanya dan saya mendapat Stack space overflowkesalahan.

Setelah melakukan beberapa pembacaan (misalnya di sini dan di sini ) saya telah mengidentifikasi dua pedoman untuk menghemat ruang tumpukan (Haskeller berpengalaman, perbaiki saya jika saya menulis sesuatu yang tidak benar):

  1. Hindari panggilan fungsi rekursif yang tidak berulang-ulang (ini berlaku untuk semua bahasa fungsional yang mendukung optimisasi panggilan-balik).
  2. Perkenalkan sequntuk memaksa evaluasi awal sub-ekspresi sehingga ekspresi tidak tumbuh terlalu besar sebelum dikurangi (ini khusus untuk Haskell, atau setidaknya untuk bahasa yang menggunakan evaluasi malas).

Setelah memasukkan lima atau enam seqpanggilan dalam kode saya, alat saya berjalan dengan lancar lagi (juga pada data yang lebih besar). Namun, saya menemukan kode asli sedikit lebih mudah dibaca.

Karena saya bukan programmer Haskell yang berpengalaman, saya ingin bertanya apakah memperkenalkan seqdengan cara ini adalah praktik yang umum, dan seberapa sering orang akan melihat seqkode produksi Haskell. Atau apakah ada teknik yang memungkinkan untuk menghindari penggunaan seqterlalu sering dan masih menggunakan sedikit ruang stack?

Giorgio
sumber
1
Optimalisasi seperti yang Anda gambarkan hampir selalu akan membuat kode sedikit kurang elegan.
Robert Harvey
@Robert Harvey: Apakah ada teknik alternatif untuk menjaga agar penggunaan tumpukan rendah? Maksud saya, saya membayangkan saya harus menulis ulang fungsi saya secara berbeda tetapi saya tidak tahu apakah ada teknik yang sudah mapan. Upaya pertama saya adalah menggunakan fungsi rekursif ekor, yang membantu tetapi tidak memungkinkan saya untuk menyelesaikan masalah saya sepenuhnya.
Giorgio

Jawaban:

17

Sayangnya ada beberapa kasus ketika seseorang harus menggunakan sequntuk mendapatkan program yang efisien / bekerja dengan baik untuk data besar. Jadi dalam banyak kasus, Anda tidak dapat melakukannya tanpa kode produksi. Anda dapat menemukan informasi lebih lanjut di Real World Haskell, Bab 25. Profiling dan optimalisasi .

Namun, ada beberapa kemungkinan cara menghindari penggunaan seqsecara langsung. Ini dapat membuat kode lebih bersih dan lebih kuat. Beberapa ide:

  1. Gunakan saluran , pipa atau iterate bukan interact. Malas IO diketahui memiliki masalah dengan mengelola sumber daya (bukan hanya memori) dan iteratee dirancang persis untuk mengatasi hal ini. (Saya sarankan untuk menghindari IO yang malas sama sekali tidak peduli seberapa besar data Anda - lihat Masalah dengan I / O malas .)
  2. Alih-alih menggunakan seqlangsung menggunakan (atau mendesain sendiri) kombinator seperti foldl ' atau foldr' atau versi ketat perpustakaan (seperti Data.Map.Strict atau Control.Monad.State.Strict ) yang dirancang untuk perhitungan yang ketat.
  3. Gunakan ekstensi BangPatterns . Memungkinkan untuk mengganti seqdengan pencocokan pola yang ketat. Mendeklarasikan bidang konstruktor yang ketat dapat berguna dalam beberapa kasus.
  4. Dimungkinkan juga untuk menggunakan Strategi untuk memaksa evaluasi. Pustaka Strategi sebagian besar ditujukan untuk komputasi paralel, tetapi memiliki metode untuk memaksa nilai ke WHNF ( rseq) atau NF penuh ( rdeepseq) juga. Ada banyak metode utilitas untuk bekerja dengan koleksi, menggabungkan strategi, dll.
Petr Pudlák
sumber
+1: Terima kasih atas petunjuk dan tautan yang bermanfaat. Poin 3 sepertinya cukup menarik (dan solusi termudah untuk saya gunakan saat ini). Mengenai saran 1, saya tidak melihat bagaimana menghindari IO yang malas dapat meningkatkan hal-hal: Sejauh yang saya mengerti IO malas harus lebih baik untuk filter yang seharusnya memproses aliran data (mungkin sangat lama).
Giorgio
2
@Giorgio Saya menambahkan tautan ke Haskell Wiki tentang masalah dengan Lazy IO. Dengan malas IO Anda dapat memiliki waktu yang sangat sulit mengelola sumber daya. Misalnya, jika Anda tidak sepenuhnya membaca input (seperti karena evaluasi malas), pegangan file tetap terbuka . Dan jika Anda pergi dan menutup pegangan file secara manual, sering terjadi karena evaluasi malas membaca itu ditunda dan Anda menutup pegangan sebelum membaca seluruh input. Dan, seringkali cukup sulit untuk menghindari masalah memori dengan IO malas.
Petr Pudlák
Saya baru-baru ini mengalami masalah ini dan program saya kehabisan file deskriptor. Jadi saya mengganti IO malas dengan IO ketat menggunakan ketat ByteString.
Giorgio