Foldr veya foldl (veya foldl ') ile ilgili sonuçlar


155

İlk olarak, okuduğum Gerçek Dünya Haskell asla kullanmamayı foldlve kullanmamayı söylüyor foldl'. Bu yüzden ona güveniyorum.

Ama ne zaman kullanacağım konusunda foldrpusluyum foldl'. Onların önümde farklı bir şekilde nasıl çalıştıklarının yapısını görebilsem de, "hangisinin daha iyi" olduğunu anlamayacak kadar aptalım. Sanırım her ikisi de aynı cevabı ürettiklerinden hangisinin kullanıldığının önemli olmaması gerektiği gibi görünüyor (değil mi?). Aslında, bu yapı ile ilgili önceki deneyimim "sol" ve "sağ" versiyonlara sahip görünmeyen Ruby's injectve Clojure's reduce. (Yan soru: hangi sürümü kullanıyorlar?)

Benim gibi akıllara meydan okuyan bir tür yardımcı olabilecek herhangi bir fikir çok takdir edilecektir!

Yanıtlar:


174

foldr f x ysNereye ys = [y1,y2,...,yk]benziyorsa özyineleme

f y1 (f y2 (... (f yk x) ...))

için özyineleme oysa foldl f x ysgibi görünüyor

f (... (f (f x y1) y2) ...) yk

Burada önemli bir fark, eğer sonucunun f x ysadece değeri kullanılarak hesaplanabiliyorsa x, o foldrzaman tüm listeyi incelemesine gerek yoktur. Örneğin

foldr (&&) False (repeat False)

Falseoysa geri döner

foldl (&&) False (repeat False)

asla sona ermez. (Not: repeat Falseher öğenin olduğu yerde sonsuz bir liste oluşturur False.)

Öte yandan, foldl'kuyruk özyinelemeli ve katıdır. Ne olursa olsun (örneğin bir listedeki sayıları toplamak) tüm listeyi geçmeniz gerektiğini biliyorsanız, o zamandan foldl'daha fazla alan (ve muhtemelen zaman) etkilidir foldr.


2
Foldr'da f y1 thunk olarak değerlendirilir, bu nedenle False değerini döndürür, ancak foldl'de f parametresinin herhangi birini bilemez. Haskell'de, kuyruk özyineleme olsun ya da olmasın, her ikisi de thunks taşmasına neden olabilir, yani thunk çok büyük. foldl 'uygulama sırasında hemen gövdeyi azaltabilir.
Sawyer

25
Karışıklığı önlemek için, parantezlerin gerçek değerlendirme sırasını göstermediğine dikkat edin. Haskell tembel olduğu için ilk olarak en dıştaki ifadeler değerlendirilecektir.
Lii

Büyük cevap. Bir liste boyunca kısmen durabilir bir kat istiyorsanız, foldr kullanmak zorunda olduğunu eklemek istiyorum; yanılmıyorsam sol katlar durdurulamaz. ("Biliyorsan ... bütün listeyi geçeceksin" derken bunu ima ediyorsun). Ayrıca, "yalnızca değeri kullanarak" yazım hatası "yalnızca değeri kullanarak" olarak değiştirilmelidir. Yani "açık" kelimesini kaldırın. (Stackoverflow 2 karakterlik bir değişiklik göndermeme izin vermedi!).
Lqueryvg

@Lqueryvg sol kıvrımları durdurmanın iki yolu: 1. sağ kıvrımla kodlayın (bkz. fodlWhile); 2. onu sol taramaya ( scanl) dönüştürün ve bunu last . takeWhile pveya benzeri ile durdurun . Ah ve 3. kullanın mapAccumL. :)
Ness

1
@Desty çünkü her adımda genel sonucunun yeni bir parçasını üretir - aksine foldl, genel sonucunu toplar ve bunu ancak tüm iş bittikten sonra üretir ve gerçekleştirilecek başka adım kalmaz. Yani foldl (flip (:)) [] [1..3] == [3,2,1], yani scanl (flip(:)) [] [1..] = [[],[1],[2,1],[3,2,1],...]... IOW foldl f z xs = last (scanl f z xs)ve sonsuz listelerin hiçbir son öğesi yoktur (yukarıdaki örnekte, kendisi INF'den 1'e kadar sonsuz bir liste olacaktır).
Ness


33

Onların semantik sadece birbirleriyle karıştırmayın böylece farklılık foldlve foldr. Biri elemanları soldan, diğeri sağdan katlar. Bu şekilde operatör farklı bir sırayla uygulanır. Bu, çıkarma gibi ilişkisel olmayan tüm işlemler için önemlidir.

Haskell.org'un bu konuda ilginç bir makalesi var.


Anlambilimleri sadece önemsiz bir şekilde farklılık gösterir, bu pratikte anlamsızdır: Kullanılan işlevin argüman sırası. Yani arayüz açısından hala değiştirilebilir olarak sayılırlar. Gerçek fark, görünüşe göre, sadece optimizasyon / uygulama.
Evi1M4chine

2
@ Evi1M4chine Farkların hiçbiri önemsiz değil. Aksine, bunlar önemlidir (ve evet, pratikte anlamlıdır). Aslında, bugün bu cevabı yazacak olsaydım, bu farkı daha da vurgulayacaktı.
Konrad Rudolph

23

Kısacası, foldrakümülatör işlevi ikinci argümanında tembel olduğunda daha iyidir. Haskell wiki'nin Stack Overflow (kelime oyunu) hakkında daha fazla bilgi edinin .


18

Nedeni tüm kullanımların% 99'u için foldl'tercih edilir, foldlçoğu kullanım için sabit alanda çalışabilmesidir.

İşlevi al sum = foldl['] (+) 0. Ne zaman foldl'kullanılır, toplamı derhal böylece uygulayarak hesaplanmaktadır sumgibi şeyler kullanıyorsanız sonsuz listeye sadece (sabit uzayda büyük olasılıkla sonsuza çalıştırın ve edecektir Ints, Doubles, Floats. IntegerS eğer sürekli alandan daha fazla kullanacak sayısı daha büyük olur maxBound :: Int).

İle foldlbir thunk oluşturulur (cevabı saklamak yerine daha sonra değerlendirilebilecek cevabı nasıl alacağınıza dair bir tarif gibi). Bu gövdeler çok yer kaplayabilir ve bu durumda, ifadeyi değerlendirmek, gövdeyi depolamaktan daha iyidir (bir yığın taşmasına neden olur ... ve sizi ... ah, boşver)

Umarım yardımcı olur.


1
Büyük istisna, geçirilen işlevin foldlargümanlarından bir veya daha fazlasına yapıcı uygulamaktan başka bir şey yapmamasıdır.
dfeuer

foldlAslında en iyi seçimin ne zaman olacağına dair genel bir kalıp var mı ? (Sonsuz listeler gibi foldryanlış seçim ne zaman , optimizasyon akıllıca.?)
Evi1M4chine

@ Evi1M4çinin foldrsonsuz listeler için yanlış seçim olarak ne demek istediğinizden emin değilim . Aslında, sonsuz listeler için foldlveya kullanmamalısınız foldl'. Yığın taşmaları ile
KevinOrr

14

Bu arada, Ruby'nin injectve Clojure en reducevardır foldl(ya foldl1kullandığınız sürümüne göre). Genellikle, bir dilde yalnızca bir form olduğunda, Python reduce, Perl List::Util::reduce, C ++ accumulate, C # Aggregate, Smalltalk inject:into:, PHP array_reduce, Mathematica Fold, vb. Dahil olmak üzere sol bir kattır, reduceancak sağ kat için bir seçenek vardır.


2
Bu yorum yardımcı olur, ancak kaynakları takdir ediyorum.
titaniumdecoy

Ortak Lisp'ler reducetembel değildir, bu yüzden foldl've buradaki düşüncelerin çoğu geçerli değildir.
57'de MicroVirus

Sanırım foldl', bunlar katı diller olduğu için değil mi? Aksi halde, bu tüm sürümlerin yığın taşmalarına neden olduğu anlamına foldlgelmez mi?
Evi1M4chine

6

Konrad'ın belirttiği gibi , anlambilimleri farklıdır. Aynı tipte bile değiller:

ghci> :t foldr
foldr :: (a -> b -> b) -> b -> [a] -> b
ghci> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
ghci> 

Örneğin, liste ekleme operatörü (++) ile gerçekleştirilebilmektedir foldrolarak

(++) = flip (foldr (:))

süre

(++) = flip (foldl (:))

size bir tür hata verecektir.


Tipleri aynı, sadece çevrilmiş, alakasız. Kötü P / NP problemi dışında arayüzleri ve sonuçları aynıdır (okuma: sonsuz listeler). ;) Uygulamadan kaynaklanan optimizasyon, pratikte anlayabildiğim kadarıyla tek farktır.
Evi1M4chine

@ Evi1M4chine Bu yanlış, şu örneğe bakın: foldl subtract 0 [1, 2, 3, 4]değerlendirir -10, foldr subtract 0 [1, 2, 3, 4]değerlendirir -2. foldlaslında 0 - 1 - 2 - 3 - 4iken foldrise 4 - 3 - 2 - 1 - 0.
krapht

@krapht, foldr (-) 0 [1, 2, 3, 4]öyle -2ve foldl (-) 0 [1, 2, 3, 4]öyle -10. Öte yandan, subtract(ne beklediğiniz geriye ise subtract 10 14ise 4böylece,) foldr subtract 0 [1, 2, 3, 4]olduğu -10ve foldl subtract 0 [1, 2, 3, 4]bir 2(pozitif).
pianoJames
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.