Tembel değerlendirme kavramı neden faydalıdır?


30

Öyle görünüyor tembel değerlendirme ifadelerin kendi kod çalıştırıldığında sırayla üzerinde kaybetmek kontrolüne bir programcı neden olabilir. Bunun neden bir programcı tarafından kabul edilebilir veya arzu edildiğini anlama konusunda sorun yaşıyorum.

Bu paradigma, bir ifadenin ne zaman ve nerede değerlendirileceğini garanti etmediğimizde, amaçlandığı şekilde çalışan öngörülebilir bir yazılım oluşturmak için nasıl kullanılabilir?


10
Çoğu durumda önemli değil. Diğerleri için sadece katılık uygulayabilirsiniz.
Cat Plus Plus,

22
Haskell gibi tamamen işlevsel dillerin amacı, kod çalıştırıldığında, yan etkisi olmadığı için uğraşmanıza gerek kalmamasıdır.
bitmask

21
"Kod yürütme" hakkında düşünmeyi bırakmanız ve "sonuçları hesaplamayı" düşünmeye başlamanız gerekir, çünkü en ilginç sorunlarda gerçekten istediğiniz şey budur. Elbette, programların genellikle çevre ile de bir şekilde etkileşime girmeleri gerekir, ancak bu genellikle kodun küçük bir kısmına indirgenebilir. Gerisi için, tamamen işlevsel olarak çalışabilirsiniz ve orada tembellik yapmak mantığı çok daha basit hale getirebilir.
leftaroundabout

6
("Neden tembel değerlendirme kullanıyorsunuz?") Başlığındaki soru, vücuttaki ("Tembel değerlendirmeyi nasıl kullanırsınız?") Sorusundan çok farklıdır. İlki için, bu soruya cevabımı görün .
Daniel Wagner

1
Tembellik yararlı olduğunda bir örnek: Haskell'de tembellik nedeniyle karmaşıklığı head . sortvardır O(n)(değil O(n log n)). Tembel Değerlendirme ve Zaman Karmaşıklığı bölümüne bakınız .
Petr Pudlák

Yanıtlar:


62

Cevapların çoğu, sonsuz listeler ve hesaplamanın değerlendirilmemiş bölümlerinden elde edilen performans kazanımları gibi konulara giriyor, ancak tembellik için daha fazla motivasyon eksik: modülerlik .

Klasik argüman, John Hughes'un "Neden İşlevsel Programlamanın Önemi" başlıklı makalesinde belirtilmiştir . Bu makaledeki anahtar örnek (Bölüm 5), alfa-beta arama algoritmasını kullanarak Tic-Tac-Toe oynuyor. Anahtar nokta (s. 9):

[Tembel değerlendirme], bir programın çok sayıda olası cevapları oluşturan bir jeneratör ve uygun olanı seçen bir seçici olarak modülerleştirilmesini pratik hale getirir.

Tic-Tac-Toe programı, belirli bir pozisyondan başlayarak tüm oyun ağacını üreten bir fonksiyon ve onu tüketen ayrı bir fonksiyon olarak yazılabilir. Çalışma zamanında bu, tüm oyun ağacını kendiliğinden oluşturmaz, yalnızca tüketicinin gerçekten ihtiyaç duyduğu alt bölümleri oluşturur. Tüketiciyi değiştirerek alternatiflerin üretilme sırasını ve kombinasyonunu değiştirebiliriz; Jeneratörü hiçbir şekilde değiştirmenize gerek yok.

İstekli bir dilde, bu şekilde yazamazsınız, çünkü muhtemelen ağacı oluşturmak için çok fazla zaman ve bellek harcarsınız. Yani sen de sonunda:

  1. Üretimin ve tüketimin aynı işleve birleştirilmesi;
  2. Sadece belirli tüketiciler için en iyi şekilde çalışan bir üretici yazmak;
  3. Kendi tembelliğinizin versiyonunu uygulamak.

Lütfen daha fazla bilgi veya bir örnek. Bu ilginç geliyor.
Alex Nye

1
@AlexNye: John Hughes gazetesinde daha fazla bilgi var. Akademik bir makale olmasına rağmen --- ve bu nedenle korkutucu olan hiç şüphesiz --- aslında çok erişilebilir ve okunabilir. Uzunluğu olmasaydı, muhtemelen burada bir cevap olarak sığacak!
Tikhon Jelvis

Belki de bu cevabı anlamak için, kişi Hughes'un makalesini okumalıdır ... bunu yapmamış, hala tembellik ve modülerliğin nasıl ve niçin ilişkili olduğunu göremiyorum.
stakx

@stakx Daha iyi bir tanım olmadan, tesadüfler dışında ilişkili görünmüyorlar. Tembelliğin bu örnekteki avantajı, tembel bir jeneratörün oyunun tüm olası durumlarını üretme yeteneğine sahip olmasıdır, ancak sadece gerçekleşenleri tüketeceğiniz için boşa harcayan zaman / hafızayı kullanmaz. Jeneratör tembel bir jeneratör olmadan tüketiciden ayrılabilir ve tüketiciden ayrılmadan tembel olması mümkündür (daha zor da olsa).
Izkata

@ İztaka: "Jeneratör tembel bir jeneratör olmadan tüketiciden ayrılabilir ve tüketiciden ayrılmadan tembel olması mümkündür." Bu rotaya giderken, ** aşırı derecede uzmanlaşmış bir jeneratör ** ile sonuçlanabileceğini unutmayın - jeneratörün bir tüketiciyi optimize etmek için yazıldığını ve diğerleri için tekrar kullanıldığında düşük olduğunu. Yaygın bir örnek, kök nesneden sadece bir dize istediğiniz için tüm nesne grafiğini alan ve oluşturan nesne ilişkisel eşleştiricilerdir. Tembellik, bu gibi pek çok vakadan kaçınır (ancak hepsini değil).
kasundim

32

Bu paradigma, bir ifadenin ne zaman ve nerede değerlendirileceğini garanti etmediğimizde, amaçlandığı şekilde çalışan öngörülebilir bir yazılım oluşturmak için nasıl kullanılabilir?

Bir ifade yan etki olmadığında, ifadelerin değerlendirilme sırası onların değerini etkilemez, bu nedenle programın davranışı sıradan etkilenmez. Dolayısıyla davranış tamamen tahmin edilebilir.

Şimdi yan etkiler farklı bir konu. Herhangi bir sırayla yan etkiler görülebilirse, programın davranışı gerçekten tahmin edilemez. Ancak bu aslında durum böyle değil. Haskell gibi tembel diller onu referans olarak şeffaf bir noktaya getirir, yani ifadelerin değerlendirilme sırasının sonuçlarını asla etkilemeyeceğinden emin olmak. Haskell'de bu, tüm işlemleri IO monadında gerçekleşmesi için kullanıcı tarafından görülebilen yan etkilerle zorlayarak gerçekleştirilir. Bu, tüm yan etkilerin tam olarak beklediğiniz sırada gerçekleşmesini sağlar.


15
Bu nedenle, sadece Haskell gibi "zorunlu saflığı" olan dillerin varsayılan olarak her yerde tembelliklerini desteklemelerinin nedeni budur. Scala gibi "teşvik edilen saflık" dilleri, programcının tembel bir tembelliği istediklerini açıkça söylemesini ister ve tembellik sorunlarına neden olmayacak şekilde programcıya kalır. Varsayılan olarak tembelliği olan ve takip edilmeyen yan etkileri olan bir dili tahmin etmek gerekirse programlamak zor olacaktır.
Ben

1
şüphesiz IO dışındaki monadlar da yan etkilere neden olabilir
jk.

1
@jk Yalnızca IO dış yan etkilere neden olabilir .
dave4420

@ dave4420 evet ama bu cevabın söylediği şey değil
jk.

1
jk Haskell aslında hayır. IO dışında hiçbir monad (veya IO üzerine inşa edilenler) yan etkileri yoktur. Ve bunun nedeni derleyicinin IO’ya farklı davranmasıdır. IO'yu "Değiştirilemez Kapalı" olarak düşünüyor. Monad'lar belirli bir yürütme emrini sağlamak için yalnızca (akıllı) bir yoldur (bu nedenle dosyanız yalnızca kullanıcı "evet" girdikten sonra silinir ).
Fularlı

22

Veritabanlarına aşina iseniz, verileri işlemek için çok sık bir yoludur:

  • Gibi bir soru sor select * from foobar
  • Daha fazla veri olsa da, yapın: bir sonraki sonuç satırına geçin ve işleyin

Sonucun nasıl üretildiğini ve hangi yolla (indeksler? Tam tablo taramaları?) Veya ne zaman kontrol edemezsiniz (tüm veriler istendiğinde bir kerede mi yoksa artımlı olarak mı üretilmeli?). Bildiğiniz tek şey: eğer daha fazla veri varsa, istediğinizde elde edersiniz.

Tembel değerlendirme, aynı şeye oldukça yakın. Yani tanımlanmış sonsuz bir listeniz var. Fibonacci dizisi - beş sayıya ihtiyacınız varsa, hesaplanan beş sayıyı alırsınız; 1000'e ihtiyacınız varsa, 1000 elde edersiniz. İşin püf noktası, çalışma zamanının nerede ve ne zaman sağlayacağını bilmesidir. Çok, çok kullanışlı.

(Java programcıları bu davranışı yineleyicilerle taklit edebilir - diğer dillerin benzer bir şeyleri olabilir)


İyi bir nokta. Örneğin Collection2.filter()(bu sınıftaki diğer yöntemlerin yanı sıra) hemen hemen tembel bir değerlendirme uygular: sonuç sıradan bir "gibi görünüyor" Collection, ancak yürütme sırası sezgisel olmayabilir (ya da en azından açık olmayan). Ayrıca, yieldPython'da (ve C # 'da benzer bir özellik ismini hatırlamıyorum) ki bu, normal bir yineleyiciden ziyade tembel değerlendirmeyi desteklemeye daha yakın.
Joachim Sauer

@JoachimSauer C # 'da verim iadesini ya da tabii ki yarısı tembel olan prebuild linq opreratorlarını kullanabilirsiniz
jk.

+1: Yinelemeleri zorunlu / nesne yönelimli bir dilde belirtmek için. Java'da akışları ve akış işlevlerini uygulamak için benzer bir çözüm kullandım. Yineleyicileri kullanarak, bilinmeyen uzunlukta bir giriş akışında take (n), dropWhile () gibi fonksiyonlara sahip olabilirim.
Giorgio

13

Veritabanınızdan, adları "Ab" ile başlayan ve 20 yıldan daha eski olan ilk 2000 kullanıcının listesini isteyin. Ayrıca erkek olmalılar.

İşte küçük bir şema.

You                                            Program Processor
------------------------------------------------------------------------------
Get the first 2000 users ---------->---------- OK!
                         --------------------- So I'll go get those records...
WAIT! Also, they have to ---------->---------- Gotcha!
start with "Ab"
                         --------------------- NOW I'll get them...
WAIT! Make sure they're  ---------->---------- Good idea Boss!
over 20!
                         --------------------- Let's go then...
And one more thing! Make ---------->---------- Anything else? Ugh!
sure they're male!

No that is all. :(       ---------->---------- FINE! Getting records!

                         --------------------- Here you go. 
Thanks Postgres, you're  ---------->----------  ...
my only friend.

Bu korkunç etkileşimin görebileceği gibi, "veritabanı" aslında tüm koşulları yerine getirmeye hazır olana kadar hiçbir şey yapmıyor. Her adımda tembel yükleme sonuçları ve her seferinde yeni koşullar uygulanıyor.

İlk 2000 kullanıcısını almak yerine, geri döndürmek, "Ab" için filtrelemek, geri döndürmek, 20'den fazla filtrelemek, geri döndürmek ve erkek için filtrelemek ve en sonunda geri döndürmek yerine.

Özetle tembel yükleme.


1
Bu gerçekten berbat bir açıklama IMHO. Maalesef, bu SE sitesinde, oy kullanmaya yetecek kadar temsilcisi bulunmuyor. Tembel değerlendirmenin asıl amacı , bu sonuçların hiçbirinin , başka bir şeyi tüketmeye hazır olana kadar üretilmediğidir.
Alnitak

Gönderdiğim cevap, yorumunuzla tamamen aynı şeyi söylüyor.
sergserg

Bu çok kibar bir Program İşlemcisi.
Julian

9

İfadelerin tembel değerlendirmesi belirli bir kod parçasının tasarımcısının kodunun yürütüldüğü sıra üzerinde kontrolünü kaybetmesine neden olur.

Tasarımcı, sonucun aynı olması şartıyla ifadelerin değerlendirilme sırasını dikkate almamalı. Değerlendirmeyi erteleyerek, bazı ifadeleri tamamen değerlendirmekten kaçınmak mümkün olabilir, bu da zaman kazandırır.

Aynı fikri işyerinde daha düşük düzeyde görebilirsiniz: birçok mikroişlemci, çeşitli yürütme ünitelerini daha verimli kullanmalarını sağlayan sıra dışı talimatları yürütebilir. Anahtar, talimatlar arasındaki bağımlılıklara bakmaları ve sonucu değiştireceği yeri yeniden sıralamaktan kaçınmalarıdır.


5

Tembel değerlendirme için zorlayıcı olduğunu düşündüğüm birkaç argüman var

  1. Modülerlik Tembel değerlendirme ile kodu parçalara ayırabilirsiniz. Örneğin, "bir liste listesindeki öğelerin ilk on karşıtını, karşıtların 1'den küçük olacağı şekilde bulma" sorununun olduğunu varsayalım. Haskell gibi bir şey yazabilirsiniz

    take 10 . filter (<1) . map (1/)
    

    ama bu kesin bir dilde yanlış, çünkü eğer verirseniz [2,3,4,5,6,7,8,9,10,11,12,0]sıfıra bölebilirsiniz. Bunun pratikte neden harika olduğu için Sacundim'in cevabına bakınız.

  2. Daha çok şey işe yarıyor Kesin olarak (pun amaçlanan) daha fazla program sıkı bir değerlendirme ile değil, katı olmayan bir değerlendirme ile son bulur. Programınız “istekli” bir değerlendirme stratejisiyle sona ererse, “tembel” bir strateji ile sona erecektir, ancak oposite doğru değildir. Bu fenomenin spesifik örnekleri olarak sonsuz veri yapıları (gerçekten sadece çok havalı) gibi şeyler elde edersiniz. Daha fazla program tembel dillerde çalışmaktadır.

  3. Optimallik İhtiyaç çağrısı değerlendirmesi zaman açısından asimptotik olarak optimaldir. Başlıca tembel diller (esasen Haskell ve Haskell), ihtiyaç duyulan çağrı vaat etmese de, optimal maliyet modelini az çok bekleyebilirsiniz. Sıkılık analizörleri (ve spekülatif değerlendirme), genel olarak pratikte ek yükü düşük tutar. Uzay daha karmaşık bir konudur.

  4. Tembel değerlendirmeyi kullanan Kuvvetler Saflık , disiplinsiz bir şekilde yan etkilerle başa çıkmayı tamamen acıtır, çünkü siz koyduğunuz gibi programcı kontrolü kaybeder. Bu iyi birşey. Referans şeffaflığı, programlar hakkında programlama, kırılma ve akıl yürütmeyi çok daha kolay hale getirir. Sıkı diller kaçınılmaz olarak saf bitlere sahip olmanın baskısını haklı çıkarır - Haskell ve Clean'ın güzelce direndiği bir şey. Bu, yan etkilerin her zaman kötü olduğunu söylemek değildir, ancak onları kontrol etmek o kadar kullanışlıdır ki, bu sebep tek başına tembel dilleri kullanmak için yeterlidir.


2

Sunulan çok pahalı hesaplamalar olduğunu varsayalım, ancak hangisine ihtiyaç duyulacağını ya da hangi sırada olacağını bilmiyorum. Tüketiciyi neyin mevcut olduğunu anlamaya ve henüz yapılmamış olan hesaplamaları tetiklemeye zorlamak için karmaşık bir anne-may-i protokolü ekleyebilirsiniz. Veya hesaplamalar tamamlanmış gibi davranan bir arabirim sunabilir.

Ayrıca, sonsuz bir sonuç aldığınızı varsayalım. Örneğin tüm asal kümeleri. Kümeyi önceden hesaplayamayacağınız açıktır, bu nedenle primiler alanındaki herhangi bir işlemin tembel olması gerekir.


1

tembel değerlendirme ile kod yürütme üzerindeki kontrolünü kaybetmezsiniz, yine de kesinlikle belirleyicidir. Yine de alışmak zor.

tembel değerlendirme, istekli değerlendirmesinin başarısız olacağı bazı durumlarda sona erecek, ancak bunun tersi geçerli olmayacak şekilde lambda terimini azaltmanın bir yoludur. Bu, 1) aslında hesaplama yapmadan önce hesaplama sonucuna bağlanmanız gerektiğinde, örneğin, döngüsel grafik yapısını oluştururken, ancak işlevsel stilde 2 yapmak istediğinizde), sonsuz veri yapısını tanımladığınızda, ancak bu yapı beslemesini çalıştırdığınızda veri yapısının sadece bir kısmını kullanmak.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.