Saya baru saja belajar cara kerja evaluasi malas dan saya bertanya-tanya: mengapa evaluasi malas tidak diterapkan di setiap perangkat lunak yang saat ini diproduksi? Mengapa masih menggunakan evaluasi yang bersemangat?
Saya baru saja belajar cara kerja evaluasi malas dan saya bertanya-tanya: mengapa evaluasi malas tidak diterapkan di setiap perangkat lunak yang saat ini diproduksi? Mengapa masih menggunakan evaluasi yang bersemangat?
Jawaban:
Evaluasi malas membutuhkan overhead pembukuan - Anda harus tahu apakah sudah dievaluasi dan hal-hal semacam itu. Evaluasi yang penuh semangat selalu dievaluasi, jadi Anda tidak perlu tahu. Ini terutama benar dalam konteks bersamaan.
Kedua, itu sepele untuk mengubah evaluasi bersemangat menjadi evaluasi malas dengan mengemasnya menjadi objek fungsi untuk dipanggil nanti, jika Anda mau.
Ketiga, evaluasi malas menyiratkan hilangnya kontrol. Bagaimana jika saya malas mengevaluasi membaca file dari disk? Atau mendapatkan waktu? Itu tidak bisa diterima.
Evaluasi yang penuh semangat bisa lebih efisien dan lebih terkendali, dan secara sepele dikonversi menjadi evaluasi yang malas. Mengapa Anda ingin evaluasi malas?
readFile
adalah persis apa yang saya butuhkan. Selain itu, mengubah dari evaluasi malas ke bersemangat sama sepele.
head [1 ..]
memberi Anda dalam bahasa murni yang dievaluasi dengan penuh semangat, karena dalam Haskell itu memberikannya 1
?
Terutama karena kode dan status malas dapat bercampur dengan buruk dan menyebabkan beberapa bug sulit ditemukan. Jika keadaan objek dependen berubah, nilai objek malas Anda dapat salah saat dievaluasi. Adalah jauh lebih baik untuk memiliki programmer secara eksplisit kode objek menjadi malas ketika dia tahu situasinya tepat.
Di samping catatan Haskell menggunakan evaluasi Malas untuk semuanya. Ini dimungkinkan karena ini adalah bahasa fungsional dan tidak menggunakan status (kecuali dalam beberapa keadaan luar biasa di mana mereka ditandai dengan jelas)
set!
penerjemah Skema malas. > :(
Evaluasi malas tidak selalu lebih baik.
Manfaat kinerja evaluasi malas bisa sangat baik, tetapi tidak sulit untuk menghindari evaluasi yang paling tidak perlu di lingkungan yang bersemangat - tentu saja malas membuatnya mudah dan lengkap, tetapi jarang evaluasi yang tidak perlu dalam kode merupakan masalah besar.
Hal yang baik tentang evaluasi malas adalah ketika memungkinkan Anda menulis kode yang lebih jelas; mendapatkan prima ke-10 dengan memfilter daftar bilangan asli tak terbatas dan mengambil elemen ke-10 dari daftar itu adalah salah satu cara yang paling ringkas dan jelas untuk melanjutkan: (pseudocode)
let numbers = [1,2...]
fun is_prime x = none (map (y-> x mod y == 0) [2..x-1])
let primes = filter is_prime numbers
let tenth_prime = first (take primes 10)
Saya percaya itu akan sangat sulit untuk mengekspresikan hal-hal begitu singkat tanpa rasa malas.
Tapi kemalasan bukanlah jawaban untuk segalanya. Sebagai permulaan, kemalasan tidak dapat diterapkan secara transparan di hadapan negara, dan saya percaya keadaan tidak dapat dideteksi secara otomatis (kecuali jika Anda bekerja di katakanlah, Haskell, ketika keadaan cukup eksplisit). Jadi, dalam kebanyakan bahasa, kemalasan perlu dilakukan secara manual, yang membuat segala sesuatunya menjadi kurang jelas dan dengan demikian menghilangkan salah satu manfaat besar dari lazy eval.
Selain itu, kemalasan memiliki kelemahan kinerja, karena menimbulkan overhead signifikan untuk menjaga ekspresi yang tidak dievaluasi; mereka menggunakan penyimpanan dan mereka lebih lambat untuk bekerja dengan nilai-nilai sederhana. Bukan hal yang aneh untuk mengetahui bahwa Anda harus menginginkan kode yang bersemangat karena versi malasnya lambat, dan terkadang sulit untuk beralasan tentang kinerja.
Karena cenderung terjadi, tidak ada strategi terbaik mutlak. Malas sangat bagus jika Anda dapat menulis kode yang lebih baik dengan mengambil keuntungan dari struktur data tak terbatas atau strategi lain yang memungkinkan Anda untuk menggunakannya, tetapi bersemangat bisa lebih mudah untuk dioptimalkan.
Berikut ini adalah perbandingan singkat pro dan kontra dari evaluasi bersemangat dan malas:
Evaluasi yang bersemangat:
Potensi overhead dari hal-hal yang tidak perlu dievaluasi.
Tanpa hambatan, evaluasi cepat.
Evaluasi malas:
Tidak ada evaluasi yang tidak perlu.
Pembukuan overhead pada setiap penggunaan suatu nilai.
Jadi, jika Anda memiliki banyak ekspresi yang tidak perlu dievaluasi, malas lebih baik; namun jika Anda tidak pernah memiliki ekspresi yang tidak perlu dievaluasi, malas adalah overhead murni.
Sekarang, mari kita lihat perangkat lunak dunia nyata: Berapa banyak fungsi yang Anda tulis tidak memerlukan evaluasi semua argumen mereka? Apalagi dengan fungsi pendek modern yang hanya melakukan satu hal, persentase fungsi termasuk dalam kategori ini sangat rendah. Dengan demikian, evaluasi yang malas hanya akan memperkenalkan overhead pembukuan sebagian besar waktu, tanpa kesempatan untuk benar-benar menyelamatkan apapun.
Akibatnya, evaluasi malas tidak membayar rata-rata, evaluasi yang penuh semangat lebih cocok untuk kode modern.
Sebagaimana dicatat oleh @DeadMG, evaluasi Malas membutuhkan biaya pembukuan. Ini bisa mahal relatif terhadap evaluasi yang penuh semangat. Pertimbangkan pernyataan ini:
i = (243 * 414 + 6562 / 435.0 ) ^ 0.5 ** 3
Ini akan membutuhkan sedikit perhitungan untuk menghitung. Jika saya menggunakan evaluasi malas, maka saya perlu memeriksa apakah sudah dievaluasi setiap kali saya menggunakannya. Jika ini di dalam loop ketat yang banyak digunakan maka overhead meningkat secara signifikan, tetapi tidak ada manfaatnya.
Dengan evaluasi yang cepat dan kompiler yang layak, rumus dihitung pada waktu kompilasi. Sebagian besar pengoptimal akan memindahkan tugas dari setiap loop yang terjadi jika sesuai.
Evaluasi malas paling cocok untuk memuat data yang jarang diakses dan memiliki overhead yang tinggi untuk diambil. Oleh karena itu lebih cocok untuk tepi kasus daripada fungsi inti.
Secara umum itu adalah praktik yang baik untuk mengevaluasi hal-hal yang sering diakses sedini mungkin. Evaluasi malas tidak bekerja dengan praktik ini. Jika Anda akan selalu mengakses sesuatu, semua evaluasi malas akan lakukan adalah menambahkan overhead. Biaya / manfaat menggunakan evaluasi malas berkurang karena item yang diakses menjadi kurang mungkin diakses.
Selalu menggunakan evaluasi malas juga menyiratkan optimasi awal. Ini adalah praktik buruk yang sering menghasilkan kode yang jauh lebih kompleks dan mahal yang mungkin terjadi. Sayangnya, optimasi prematur seringkali menghasilkan kode yang kinerjanya lebih lambat daripada kode yang lebih sederhana. Sampai Anda dapat mengukur efek optimasi, adalah ide yang buruk untuk mengoptimalkan kode Anda.
Menghindari optimasi prematur tidak bertentangan dengan praktik pengkodean yang baik. Jika praktik yang baik tidak diterapkan, optimisasi awal dapat terdiri dari penerapan praktik pengkodean yang baik seperti memindahkan perhitungan di luar lingkaran.
Jika kita berpotensi harus sepenuhnya mengevaluasi ekspresi untuk menentukan nilainya, maka evaluasi malas dapat merugikan. Katakanlah kita memiliki daftar panjang nilai boolean dan kami ingin mengetahui apakah semuanya benar:
[True, True, True, ... False]
Untuk melakukan ini, kita harus melihat setiap elemen dalam daftar, apa pun yang terjadi, jadi tidak ada kemungkinan untuk memotong evaluasi dengan malas. Kita bisa menggunakan lipatan untuk menentukan apakah semua nilai boolean dalam daftar benar. Jika kita menggunakan hak lipatan, yang menggunakan evaluasi malas, kita tidak mendapatkan manfaat dari evaluasi malas karena kita harus melihat setiap elemen dalam daftar:
foldr (&&) True [True, True, True, ... False]
> 0.27 secs
Lipatan kanan akan jauh lebih lambat dalam kasus ini daripada lipatan kiri yang ketat, yang tidak menggunakan evaluasi malas:
foldl' (&&) True [True, True, True, ... False]
> 0.09 secs
Alasannya adalah lipatan ketat kiri menggunakan rekursi ekor, yang berarti ia mengakumulasi nilai kembali dan tidak membangun dan menyimpan dalam rantai operasi besar memori. Ini jauh lebih cepat daripada lazy fold karena kedua fungsi tetap harus melihat seluruh daftar dan flip kanan tidak dapat menggunakan rekursi ekor. Jadi, intinya adalah, Anda harus menggunakan apa pun yang terbaik untuk tugas yang ada.