Sebagian besar karena ini bisa lebih efisien - nilai tidak perlu dihitung jika tidak akan digunakan. Misalnya, saya dapat meneruskan tiga nilai ke dalam suatu fungsi, tetapi bergantung pada urutan ekspresi kondisional, hanya subset yang benar-benar dapat digunakan. Dalam bahasa seperti C, ketiga nilai tersebut akan tetap dihitung; tetapi di Haskell, hanya nilai yang diperlukan yang dihitung.
Ini juga memungkinkan untuk hal-hal keren seperti daftar tak terbatas. Saya tidak dapat memiliki daftar yang tidak terbatas dalam bahasa seperti C, tetapi di Haskell, itu tidak masalah. Daftar tak terbatas cukup sering digunakan dalam bidang matematika tertentu, sehingga kemampuan untuk memanipulasinya dapat bermanfaat.
Contoh evaluasi malas yang berguna adalah penggunaan
quickSort
:Jika sekarang kita ingin menemukan daftar minimum, kita dapat menentukan
Yang pertama mengurutkan daftar dan kemudian mengambil elemen pertama dari daftar. Namun, karena evaluasi malas, hanya head yang dihitung. Misalnya, jika kita mengambil jumlah minimum dari daftar,
[2, 1, 3,]
quickSort akan terlebih dahulu memfilter semua elemen yang lebih kecil dari dua. Kemudian quickSort melakukan itu (mengembalikan daftar tunggal [1]) yang sudah cukup. Karena evaluasi malas, sisanya tidak pernah diurutkan, menghemat banyak waktu komputasi.Ini tentu saja contoh yang sangat sederhana, tetapi kemalasan bekerja dengan cara yang sama untuk program yang sangat besar.
Namun, ada sisi negatifnya: menjadi lebih sulit untuk memprediksi kecepatan runtime dan penggunaan memori program Anda. Ini tidak berarti bahwa program malas lebih lambat atau membutuhkan lebih banyak memori, tetapi ada baiknya untuk mengetahuinya.
sumber
take k $ quicksort list
hanya membutuhkan waktu O (n + k log k), di manan = length list
. Dengan jenis perbandingan non-malas, ini akan selalu membutuhkan waktu O (n log n).Saya menemukan evaluasi malas berguna untuk sejumlah hal.
Pertama, semua bahasa lazy yang ada adalah murni, karena sangat sulit untuk menjelaskan efek samping dalam bahasa lazy.
Bahasa murni memungkinkan Anda bernalar tentang definisi fungsi menggunakan penalaran persamaan.
Sayangnya, dalam setelan non-malas, lebih banyak pernyataan yang gagal ditampilkan daripada setelan malas, jadi ini kurang berguna dalam bahasa seperti ML. Tapi dalam bahasa malas Anda bisa dengan aman bernalar tentang kesetaraan.
Kedua, banyak hal seperti 'batasan nilai' di ML tidak diperlukan dalam bahasa lazy seperti Haskell. Ini mengarah ke deklarasi sintaks yang bagus. Bahasa seperti ML perlu menggunakan kata kunci seperti var atau fun. Di Haskell hal-hal ini runtuh menjadi satu gagasan.
Ketiga, kemalasan memungkinkan Anda menulis kode yang sangat fungsional yang dapat dipahami dalam beberapa bagian. Di Haskell, hal umum untuk menulis badan fungsi seperti:
Ini memungkinkan Anda bekerja 'dari atas ke bawah' meskipun pemahaman tentang tubuh suatu fungsi. Bahasa seperti ML memaksa Anda untuk menggunakan
let
yang dievaluasi secara ketat. Akibatnya, Anda tidak berani 'mengangkat' klausa let keluar ke bagian utama fungsi, karena jika mahal (atau memiliki efek samping) Anda tidak ingin selalu dievaluasi. Haskell dapat 'mendorong' detail ke klausa where secara eksplisit karena ia tahu bahwa konten klausa tersebut hanya akan dievaluasi sesuai kebutuhan.Dalam praktiknya, kita cenderung menggunakan pelindung dan menutupnya lebih jauh untuk:
Keempat, kemalasan terkadang menawarkan ekspresi algoritme tertentu yang jauh lebih elegan. 'Pengurutan cepat' yang malas di Haskell bersifat one-liner dan memiliki keuntungan bahwa jika Anda hanya melihat beberapa item pertama, Anda hanya membayar biaya yang sebanding dengan biaya pemilihan item tersebut saja. Tidak ada yang mencegah Anda melakukan ini secara ketat, tetapi Anda mungkin harus mengodekan ulang algoritme setiap kali untuk mencapai kinerja asimtotik yang sama.
Kelima, kemalasan memungkinkan Anda menentukan struktur kontrol baru dalam bahasa tersebut. Anda tidak dapat menulis 'if .. then .. else ..' seperti konstruksi baru dalam bahasa yang ketat. Jika Anda mencoba untuk mendefinisikan fungsi seperti:
dalam bahasa yang ketat maka kedua cabang akan dievaluasi terlepas dari nilai kondisinya. Ini menjadi lebih buruk ketika Anda mempertimbangkan loop. Semua solusi yang ketat membutuhkan bahasa untuk memberi Anda semacam kutipan atau konstruksi lambda eksplisit.
Akhirnya, dengan nada yang sama, beberapa mekanisme terbaik untuk menangani efek samping dalam sistem tipe, seperti monad, benar-benar hanya dapat diekspresikan secara efektif dalam pengaturan malas. Hal ini dapat dilihat dengan membandingkan kompleksitas Alur Kerja F # dengan Haskell Monads. (Anda dapat mendefinisikan monad dalam bahasa yang ketat, tetapi sayangnya Anda akan sering gagal satu atau dua hukum monad karena kurangnya kemalasan dan Alur Kerja dengan perbandingan mengambil banyak bagasi ketat.)
sumber
let
adalah binatang yang berbahaya, dalam skema R6RS memungkinkan#f
kemunculan acak dalam istilah Anda di mana pun mengikat simpul secara ketat mengarah ke siklus! Tidak ada permainan kata-kata yang dimaksudkan, tetapilet
binding yang lebih rekursif sangat masuk akal dalam bahasa malas. Keketatan juga memperburuk fakta bahwawhere
tidak ada cara untuk memesan efek relatif sama sekali, kecuali oleh SCC, itu adalah konstruksi tingkat pernyataan, efeknya dapat terjadi dalam urutan apa pun secara ketat, dan bahkan jika Anda memiliki bahasa murni, Anda akan berakhir dengan#f
isu. Ketatwhere
memecahkan kode Anda dengan masalah non-lokal.ifFunc(True, x, y)
akan mengevaluasi keduanyax
dany
bukannya hanyax
.Ada perbedaan antara evaluasi pesanan normal dan evaluasi malas (seperti di Haskell).
Mengevaluasi ekspresi berikut ...
... dengan evaluasi penuh semangat:
... dengan evaluasi pesanan normal:
... dengan evaluasi malas:
Itu karena evaluasi malas melihat pohon sintaks dan melakukan transformasi pohon ...
... sedangkan evaluasi urutan normal hanya melakukan perluasan tekstual.
Itulah mengapa kami, ketika menggunakan evaluasi malas, menjadi lebih kuat (evaluasi berakhir lebih sering daripada strategi lain) sementara kinerjanya setara dengan evaluasi bersemangat (setidaknya dalam notasi-O).
sumber
Evaluasi malas terkait CPU dengan cara yang sama seperti pengumpulan sampah yang terkait dengan RAM. GC memungkinkan Anda untuk berpura-pura memiliki jumlah memori yang tidak terbatas dan dengan demikian meminta objek dalam memori sebanyak yang Anda butuhkan. Waktu proses akan secara otomatis mengambil kembali objek yang tidak dapat digunakan. LE memungkinkan Anda berpura-pura memiliki sumber daya komputasi tak terbatas - Anda dapat melakukan komputasi sebanyak yang Anda butuhkan. Waktu proses tidak akan menjalankan komputasi yang tidak perlu (untuk kasus tertentu).
Apa keuntungan praktis dari model "berpura-pura" ini? Ini melepaskan pengembang (sampai batas tertentu) dari mengelola sumber daya dan menghapus beberapa kode boilerplate dari sumber Anda. Tetapi yang lebih penting adalah Anda dapat menggunakan kembali solusi Anda secara efisien dalam konteks yang lebih luas.
Bayangkan Anda memiliki daftar angka S dan angka N. Anda perlu mencari yang paling dekat dengan angka N angka M dari daftar S. Anda dapat memiliki dua konteks: satu N dan beberapa daftar L dari Ns (ei untuk setiap N di L Anda mencari M terdekat di S). Jika Anda menggunakan evaluasi malas, Anda dapat mengurutkan S dan menerapkan pencarian biner untuk menemukan M terdekat ke N. Untuk pengurutan malas yang baik, diperlukan langkah O (ukuran (S)) untuk satu N dan O (ln (ukuran (S)) * (size (S) + size (L))) langkah-langkah untuk L. yang terdistribusi merata. Jika Anda tidak memiliki evaluasi malas untuk mencapai efisiensi optimal, Anda harus mengimplementasikan algoritme untuk setiap konteks.
sumber
Jika Anda yakin Simon Peyton Jones, malas evaluasi tidak penting per se tapi hanya sebagai 'baju rambut' yang memaksa desainer untuk menjaga bahasa murni. Saya menemukan diri saya bersimpati pada sudut pandang ini.
Richard Bird, John Hughes, dan Ralf Hinze mampu melakukan hal-hal menakjubkan dengan evaluasi malas. Membaca karya mereka akan membantu Anda menghargainya. Titik awal yang baik adalah pemecah Sudoku yang luar biasa dari Bird dan makalah Hughes tentang Why Functional Programming Matters .
sumber
IO
monad) tanda tanganmain
akanString -> String
dan Anda sudah bisa menulis program interaktif dengan benar.IO
monad?Pertimbangkan program tic-tac-toe. Ini memiliki empat fungsi:
Ini menciptakan pemisahan perhatian yang jelas dan bagus. Khususnya fungsi pembuatan gerakan dan fungsi evaluasi papan adalah satu-satunya yang perlu memahami aturan permainan: fungsi pohon bergerak dan fungsi minimum dapat digunakan kembali sepenuhnya.
Sekarang mari kita coba menerapkan catur alih-alih tic-tac-toe. Dalam bahasa "bersemangat" (yaitu konvensional) ini tidak akan berfungsi karena pohon pemindahan tidak akan muat dalam memori. Jadi sekarang fungsi evaluasi papan dan generasi bergerak perlu dicampur dengan pohon bergerak dan logika minimax karena logika minimax harus digunakan untuk memutuskan gerakan mana yang akan dihasilkan. Struktur modular bersih kami yang bagus menghilang.
Namun dalam bahasa lazy, elemen pohon pemindahan hanya dihasilkan sebagai respons terhadap permintaan dari fungsi minimax: seluruh pohon pemindahan tidak perlu dibuat sebelum kami melepaskan minimax di elemen atas. Jadi, struktur modular bersih kami masih berfungsi di game nyata.
sumber
Berikut adalah dua poin lagi yang saya tidak percaya telah diangkat dalam diskusi.
Kemalasan adalah mekanisme sinkronisasi dalam lingkungan yang bersamaan. Ini adalah cara yang ringan dan mudah untuk membuat referensi ke beberapa komputasi, dan membagikan hasilnya di antara banyak utas. Jika beberapa utas mencoba mengakses nilai yang tidak dievaluasi, hanya satu dari mereka yang akan mengeksekusinya, dan yang lain akan memblokir sesuai, menerima nilai setelah tersedia.
Kemalasan sangat penting untuk amortisasi struktur data dalam pengaturan murni. Hal ini dijelaskan oleh Okasaki dalam Struktur Data Fungsional Murni secara mendetail, tetapi gagasan dasarnya adalah bahwa evaluasi malas adalah bentuk mutasi terkontrol yang penting untuk memungkinkan kita mengimplementasikan tipe struktur data tertentu secara efisien. Sementara kita sering berbicara tentang kemalasan yang memaksa kita untuk memakai kaos rambut yang murni, cara lain juga berlaku: mereka adalah sepasang fitur bahasa yang sinergis.
sumber
Ketika Anda menyalakan komputer dan Windows menahan diri dari membuka setiap direktori pada hard drive Anda di Windows Explorer dan menahan diri untuk tidak meluncurkan setiap program yang diinstal pada komputer Anda, sampai Anda menunjukkan bahwa direktori tertentu diperlukan atau program tertentu diperlukan, yang adalah evaluasi "malas".
Evaluasi "malas" adalah melakukan operasi kapan dan sesuai kebutuhan. Ini berguna jika merupakan fitur dari pustaka atau bahasa pemrograman karena umumnya lebih sulit untuk mengimplementasikan evaluasi malas sendiri daripada hanya menghitung sebelumnya semuanya di depan.
sumber
Pertimbangkan ini:
Metode doSomething () akan dijalankan hanya jika conditionOne benar dan conditionTwo benar. Dalam kasus di mana conditionOne salah, mengapa Anda perlu menghitung hasil dari conditionTwo? Evaluasi conditionTwo akan membuang-buang waktu dalam hal ini, terutama jika kondisi Anda adalah hasil dari beberapa proses metode.
Itulah salah satu contoh minat evaluasi malas ...
sumber
Ini dapat meningkatkan efisiensi. Ini yang terlihat jelas, tapi sebenarnya bukan yang paling penting. (Perhatikan juga bahwa kemalasan juga dapat mematikan efisiensi - fakta ini tidak langsung terlihat. Namun, dengan menyimpan banyak hasil sementara daripada langsung menghitungnya, Anda dapat menggunakan sejumlah besar RAM.)
Ini memungkinkan Anda menentukan konstruksi kontrol aliran dalam kode tingkat pengguna normal, daripada di-hardcode ke dalam bahasa. (Misalnya, Java memiliki
for
loop; Haskell memilikifor
fungsi. Java memiliki penanganan pengecualian; Haskell memiliki berbagai jenis pengecualian monad. C # memilikigoto
; Haskell memiliki monad lanjutan ...)Ini memungkinkan Anda memisahkan algoritme untuk menghasilkan data dari algoritme untuk memutuskan berapa banyak data yang akan dihasilkan. Anda dapat menulis satu fungsi yang menghasilkan daftar hasil nosional-tak terbatas, dan fungsi lain yang memproses sebanyak mungkin daftar ini sesuai kebutuhannya. Lebih tepatnya, Anda dapat memiliki lima fungsi generator dan lima fungsi konsumen, dan Anda dapat secara efisien menghasilkan kombinasi apa pun - alih-alih mengkodekan fungsi 5 x 5 = 25 secara manual yang menggabungkan kedua tindakan sekaligus. (!) Kita semua tahu bahwa decoupling adalah hal yang baik.
Ini kurang lebih memaksa Anda untuk merancang bahasa fungsional murni . Selalu menggoda untuk mengambil jalan pintas, tetapi dalam bahasa malas, ketidakmurnian sekecil apa pun membuat kode Anda sangat tidak dapat diprediksi, yang sangat menghalangi pengambilan jalan pintas.
sumber
Salah satu manfaat besar kemalasan adalah kemampuan untuk menulis struktur data yang tidak dapat diubah dengan batas amortisasi yang wajar. Contoh sederhananya adalah tumpukan yang tidak dapat diubah (menggunakan F #):
Kodenya masuk akal, tetapi menambahkan dua tumpukan x dan y membutuhkan waktu O (panjang x) dalam kasus terbaik, terburuk, dan rata-rata. Menambahkan dua tumpukan adalah operasi monolitik, ini menyentuh semua node di tumpukan x.
Kita dapat menulis ulang struktur data sebagai tumpukan malas:
lazy
bekerja dengan menangguhkan evaluasi kode dalam konstruktornya. Setelah dievaluasi menggunakan.Force()
, nilai yang dikembalikan disimpan dalam cache dan digunakan kembali pada setiap berikutnya.Force()
.Dengan versi malas, menambahkan adalah operasi O (1): mengembalikan 1 node dan menangguhkan pembuatan ulang daftar yang sebenarnya. Saat Anda mendapatkan kepala dari daftar ini, itu akan mengevaluasi isi simpul, memaksanya mengembalikan kepala dan membuat satu suspensi dengan elemen yang tersisa, jadi mengambil kepala daftar adalah operasi O (1).
Jadi, daftar malas kita selalu dalam keadaan membangun kembali, Anda tidak perlu membayar biaya untuk membangun kembali daftar ini sampai Anda menjelajahi semua elemennya. Menggunakan kemalasan, daftar ini mendukung O (1) consing dan appending. Menariknya, karena kami tidak mengevaluasi node hingga node diakses, sangat mungkin untuk membuat daftar dengan elemen yang berpotensi tidak terbatas.
Struktur data di atas tidak memerlukan node untuk dihitung ulang pada setiap traversal, sehingga sangat berbeda dari vanilla IEnumerables di .NET.
sumber
Cuplikan ini menunjukkan perbedaan antara evaluasi malas dan tidak malas. Tentu saja fungsi fibonacci ini sendiri dapat dioptimalkan dan menggunakan evaluasi malas daripada rekursi, tapi itu akan merusak contoh.
Misalkan kita MUNGKIN harus menggunakan 20 angka pertama untuk sesuatu, tanpa evaluasi malas, semua 20 angka harus dibuat dimuka, tetapi, dengan evaluasi malas, angka tersebut akan dibuat sesuai kebutuhan saja. Dengan demikian Anda hanya akan membayar harga kalkulasi saat dibutuhkan.
Keluaran sampel
sumber
Evaluasi malas paling berguna dengan struktur data. Anda dapat mendefinisikan sebuah larik atau vektor secara induktif dengan menetapkan hanya titik-titik tertentu dalam struktur dan mengekspresikan semua yang lain dalam kerangka keseluruhan larik. Ini memungkinkan Anda menghasilkan struktur data dengan sangat ringkas dan dengan kinerja waktu proses yang tinggi.
Untuk melihat ini beraksi, Anda dapat melihat perpustakaan jaringan saraf saya yang disebut insting . Itu membuat banyak penggunaan evaluasi malas untuk keanggunan dan kinerja tinggi. Misalnya, saya benar-benar menyingkirkan kalkulasi aktivasi imperatif tradisional. Ekspresi malas yang sederhana melakukan segalanya untukku.
Ini digunakan misalnya dalam fungsi aktivasi dan juga dalam algoritma pembelajaran propagasi mundur (saya hanya dapat memposting dua tautan, jadi Anda harus mencari sendiri
learnPat
fungsi diAI.Instinct.Train.Delta
modul). Secara tradisional keduanya membutuhkan algoritma iteratif yang jauh lebih rumit.sumber
Orang lain sudah memberikan semua alasan besarnya, tapi menurut saya latihan yang berguna untuk membantu memahami mengapa kemalasan itu penting adalah mencoba dan menulis fungsi titik tetap dalam bahasa yang ketat.
Di Haskell, fungsi titik tetap sangat mudah:
ini berkembang menjadi
tetapi karena Haskell malas, rangkaian komputasi tak terbatas itu bukanlah masalah; evaluasi dilakukan "dari luar ke dalam", dan semuanya bekerja dengan sangat baik:
Yang penting, yang penting bukan
fix
malas, tapif
malas. Begitu Anda sudah diberi aturan ketatf
, Anda bisa mengangkat tangan ke udara dan menyerah, atau eta mengembangkannya dan mengacaukan semuanya. (Ini sangat mirip dengan apa yang dikatakan Nuh tentang perpustakaan yang ketat / malas, bukan bahasanya).Sekarang bayangkan menulis fungsi yang sama dalam Scala yang ketat:
Anda tentu saja mendapatkan stack overflow. Jika Anda ingin berhasil, Anda perlu membuat
f
argumen panggilan-oleh-kebutuhan:sumber
Saya tidak tahu bagaimana Anda saat ini memikirkan berbagai hal, tetapi saya merasa berguna untuk memikirkan evaluasi malas sebagai masalah perpustakaan daripada fitur bahasa.
Maksud saya, dalam bahasa yang ketat, saya dapat mengimplementasikan evaluasi malas dengan membangun beberapa struktur data, dan dalam bahasa malas (setidaknya Haskell), saya dapat meminta ketegasan saat saya menginginkannya. Oleh karena itu, pilihan bahasa tidak benar-benar membuat program Anda malas atau tidak malas, tetapi hanya memengaruhi program yang Anda dapatkan secara default.
Setelah Anda memikirkannya seperti itu, pikirkan semua tempat di mana Anda menulis struktur data yang nantinya dapat Anda gunakan untuk menghasilkan data (tanpa melihatnya terlalu banyak sebelumnya), dan Anda akan melihat banyak kegunaan untuk malas. evaluasi.
sumber
Eksploitasi paling berguna dari evaluasi malas yang saya gunakan adalah fungsi yang disebut serangkaian sub-fungsi dalam urutan tertentu. Jika salah satu dari sub-fungsi ini gagal (dikembalikan salah), fungsi pemanggil harus segera dikembalikan. Jadi saya bisa melakukannya dengan cara ini:
atau, solusi yang lebih elegan:
Setelah Anda mulai menggunakannya, Anda akan melihat peluang untuk menggunakannya lebih dan lebih sering.
sumber
Tanpa evaluasi malas Anda tidak akan diizinkan untuk menulis sesuatu seperti ini:
sumber
Antara lain, bahasa lazy memungkinkan struktur data tak hingga multidimensi.
Sementara skema, python, dll memungkinkan struktur data tak terbatas berdimensi tunggal dengan aliran, Anda hanya dapat melintasi sepanjang satu dimensi.
Kemalasan berguna untuk masalah pinggiran yang sama , tetapi perlu diperhatikan koneksi coroutines yang disebutkan dalam tautan itu.
sumber
Evaluasi malas adalah penalaran persamaan orang miskin (yang dapat diharapkan, idealnya, untuk mendeduksi properti kode dari properti tipe dan operasi yang terlibat).
Contoh di mana itu bekerja dengan cukup baik:
sum . take 10 $ [1..10000000000]
. Yang kami tidak keberatan direduksi menjadi jumlah 10 angka, bukan hanya satu perhitungan numerik langsung dan sederhana. Tanpa evaluasi malas tentu saja ini akan membuat daftar raksasa di memori hanya untuk menggunakan 10 elemen pertamanya. Ini pasti akan sangat lambat, dan mungkin menyebabkan kesalahan kehabisan memori.Contoh di mana itu tidak besar seperti yang kita inginkan:
sum . take 1000000 . drop 500 $ cycle [1..20]
. Yang benar-benar akan menjumlahkan 1.000.000 angka, bahkan jika dalam lingkaran, bukan dalam daftar; masih harus direduksi menjadi hanya satu kalkulasi numerik langsung, dengan sedikit kondisional dan sedikit rumus. Yang akan jauh lebih baik daripada menjumlahkan 1.000.000 angka. Bahkan jika dalam satu lingkaran, dan tidak dalam daftar (yaitu setelah optimasi deforestasi).Hal lain adalah, memungkinkan untuk membuat kode dalam gaya modulo kontra rekursi ekor , dan itu hanya berfungsi .
cf. jawaban terkait .
sumber
Jika dengan "evaluasi malas" yang Anda maksud seperti di boolean gabungan, seperti di
maka jawabannya sederhana saja bahwa semakin sedikit siklus CPU yang dikonsumsi program, semakin cepat program tersebut berjalan ... dan jika sejumlah instruksi pemrosesan tidak akan berdampak pada hasil program maka itu tidak perlu, (dan karenanya sia-sia waktu) untuk melakukannya ...
jika otoh, maksud Anda apa yang saya kenal sebagai "penginisialisasi malas", seperti dalam:
Nah, teknik ini memungkinkan kode klien menggunakan kelas untuk menghindari kebutuhan memanggil database untuk catatan data Supervisor kecuali ketika klien yang menggunakan objek Employee memerlukan akses ke data supervisor ... ini membuat proses membuat instance Employee lebih cepat, namun saat Anda membutuhkan Supervisor, panggilan pertama ke properti Supervisor akan memicu panggilan Database dan data akan diambil dan tersedia ...
sumber
Kutipan dari fungsi orde tinggi
sumber