Birisi Haskell'in notunun arkasındaki kavramı açıklayabilir mi?


12

(soruyu buraya yazıyorum çünkü kodlama probleminden ziyade kavramsal mekaniği ile ilgili)

Eşitliğinde bir dizi fibonacci numarası kullanan küçük bir program üzerinde çalışıyordum, ancak belirli bir sayıyı aşarsam acı verici bir şekilde yavaşladığını fark ettim, Haskell'de bir tekniğe rastladım Memoization, kodun şu şekilde çalıştığını gösterdiler:

-- Traditional implementation of fibonacci, hangs after about 30
slow_fib :: Int -> Integer
slow_fib 0 = 0
slow_fib 1 = 1
slow_fib n = slow_fib (n-2) + slow_fib (n-1)

-- Memorized variant is near instant even after 10000
memoized_fib :: Int -> Integer
memoized_fib = (map fib [0 ..] !!)
   where fib 0 = 0
         fib 1 = 1
         fib n = memoized_fib (n-2) + memoized_fib (n-1)

Yani size sorum şu: bu nasıl ya da daha doğrusu çalışıyor?

Bir şekilde hesaplama yakalanmadan önce listenin çoğunu çalıştırmayı başarıyor mu? Ancak haskell tembelse, yakalanması gereken herhangi bir hesaplama yoktur ... Peki nasıl çalışır?


1
ne demek istediğini açıklığa kavuşturabilir misin the calculation catches up? BTW,
notlandırma haskell'e

killan'ın cevabı altında açıklamamı görün
Electric Coffee

2
Sorunuzu seviyorum; Sadece küçük bir not: teknik not denir ı değil not, zation ri kıymetleştirme.
Racheet

Yanıtlar:


11

Sadece gerçek hatırlamanın arkasındaki mekaniği açıklamak için,

memo_fib = (map fib [1..] !!)

"gövdelerin", değerlendirilmemiş hesaplamaların bir listesini üretir. Bunları açılmamış hediyeler gibi düşünün, onlara dokunmadığımız sürece koşmazlar.

Şimdi bir kez bir thunk'ı değerlendirirsek, bir daha asla değerlendirmeyiz. Bu aslında "normal" haskellerin tek mutasyon formudur, thunks bir kez somut değerler olarak değerlendirilir.

Kodunuza geri döndüğünüzde, bir thunks listeniz var ve yine de bu ağaç özyinelemesini yapıyorsunuz, ancak listeyi kullanarak tekrar gönderiyorsunuz ve listedeki bir öğe değerlendirildiğinde, bir daha asla hesaplanmaz. Böylece saf fib fonksiyonunda ağaç özyinelemesinden kaçınırız.

Teğetsel olarak ilginç bir not olarak, bu bir dizi fibonnaci sayısında özellikle hızlıdır, çünkü bu liste sadece bir kez değerlendirilir, yani memo_fib 10000iki kez hesaplarsanız , ikinci zamanın anlık olması gerekir. Bunun nedeni, Haskell'in işlevlere yönelik argümanları yalnızca bir kez değerlendirmesi ve lambda yerine kısmi uygulama kullanmanızdır.

TLDR: Hesaplamaları bir listede saklayarak, listenin her bir elemanı bir kez değerlendirilir, bu nedenle her fibonnacci numarası tüm program boyunca tam olarak bir kez hesaplanır.

Görselleştirme:

 [THUNK_1, THUNK_2, THUNK_3, THUNK_4, THUNK_5]
 -- Evaluating THUNK_5
 [THUNK_1, THUNK_2, THUNK_3, THUNK_4, THUNK_3 + THUNK_4]
 [THUNK_1, THUNK_2, THUNK_1 + THUNK_2, THUNK_4, THUNK_3 + THUNK_4]
 [1, 1, 1 + 1, THUNK_4, THUNK_3 + THUNK_4]
 [1, 1, 2, THUNK_4, 2 + THUNK4]
 [1, 1, 2, 1 + 2, 2 + THUNK_4]
 [1, 1, 2, 3, 2 + 3]
 [1, 1, 2, 3, 5]

Böylece THUNK_4, alt ifadeleri zaten değerlendirildiğinden değerlendirmenin nasıl daha hızlı olduğunu görebilirsiniz .


listedeki değerlerin kısa bir dizi için nasıl davrandığına dair bir örnek verebilir misiniz? Sanırım bunun nasıl çalışması gerektiğinin görselleştirilmesine katkıda bulunabilir ... Ve memo_fibaynı değeri iki kez ararsam, ikinci kez anında olur, ancak 1 değeri daha yüksek olursa, hala değerlendirmek sonsuza dek sürer (örneğin 30'dan 31'e kadar gitmek gibi)
Electric Coffee

@ElectricCoffee Eklendi
Daniel Gratzer

@ElectricCoffee Hayır, o zamandan beri değerlendirilmeyecek memo_fib 29ve memo_fib 30zaten değerlendirilecek, tam olarak bu iki sayıyı eklemek için gereken süreyi alacak :) Bir şey değerlendirildiğinde, değerlendirilmeye devam ediyor.
Daniel Gratzer

1
@ElectricCoffee Özyinelemeniz listeden geçmelidir, aksi takdirde performans
kazanmazsınız

2
@ElectricCoffee Evet. ancak listenin 31. öğesi geçmiş hesaplamaları kullanmıyor, evet, ama oldukça yararsız bir şekilde hatırlıyorsunuz. Tekrarlanan hesaplamalar iki kez hesaplanmaz, ancak her yeni değer için yine de ağaç yinelemesine sahipsiniz çok, çok yavaş
Daniel Gratzer

1

Notlama noktası asla aynı işlevi iki kez hesaplamak değildir - bu tamamen işlevsel olan, yani yan etkileri olmayan hesaplamaları hızlandırmak için son derece yararlıdır, çünkü bunlar için süreç doğruluğu etkilemeden tamamen otomatikleştirilebilir. Bu, saf bir şekilde uygulandığında ağaç özyinelemesine , yani üstel çabaya fiboyol açan gibi işlevler için özellikle gereklidir . (Fibonacci sayılarının özyineleme öğretimi için aslında çok kötü bir örnek olmasının bir nedeni budur - öğreticilerde veya kitaplarda bulduğunuz neredeyse tüm demo uygulamaları büyük giriş değerleri için kullanılamaz.)

Yürütme akışını izlerseniz, ikinci durumda, değerinin yürütüldüğünde fib xher zaman kullanılabilir olacağını ve çalışma zamanı sisteminin başka bir özyinelemeli çağrı yerine bellekten okuyabileceğini göreceksiniz. ilk çözüm, daha küçük değerler için sonuçlar elde edilmeden önce daha büyük çözümü hesaplamaya çalışır. Bunun nedeni, sonuçta yineleyicinin soldan sağa değerlendirilmesi ve bu nedenle ile başlamasıdır , ilk örnekteki özyineleme ile başlar ve ancak sonra sorar . Bu, birçok gereksiz birçok yinelenen işlev çağrısına yol açar.fib x+1[0..n]0nn-1


Ah bunun anlamını anlıyorum, sadece kodda görebildiğim gibi nasıl çalıştığını anlamadım, memorized_fib 20örneğin yazdığınızda map fib [0..] !! 20, aslında sadece yazıyorsunuz , yine de 20'ye kadar sayı aralığının tamamı mı yoksa burada bir şey mi kaçırıyorum?
Electric Coffee

1
Evet, ancak her numara için yalnızca bir kez. Saf uygulama fib 2o kadar sık hesaplar ki başınızı döndürür - devam edin, çağrı ağacı kürkünü sadece küçük bir değer olarak yazın n==5. Sizi neyin kurtardığını gördükten sonra bir daha asla hatırlamayı unutmayacaksınız.
Kilian Foth

@ElectricCoffee: Evet, 1'den 20'ye kadar fib hesaplar. Bu çağrıdan hiçbir şey kazanmazsınız. Şimdi fib 21'i hesaplamayı deneyin ve 1-21 hesaplamak yerine sadece 21 hesaplayabileceğinizi göreceksiniz, çünkü zaten 1-20 hesaplamanız var ve tekrar yapmanıza gerek yok.
Phoshi

Çağrı ağacını yazmaya çalışıyorum n = 5ve şu anda n == 3çok iyi olan noktaya geldim , ama belki de bunu düşünmek sadece benim zorunlu aklım, ama bunun için n == 3sadece map fib [0..]!!3? hangisi daha sonra fib nprogramın şubesine girer ... önceden hesaplanmış verilerden tam olarak nereden yararlanabilirim?
Electric Coffee

1
Hayır, memoized_fibiyi. Bunu slow_fibizlerseniz ağlatırsınız.
Kilian Foth
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.