Nee, dat vind ik ook, maar toevallig zeg ik elke keer dat ik m'n eigen versie beter vind; dat zou verkeerd over kunnen komen.

Opvallend dat er zulke grote verschillen zijn, ten eerste tussen de executie in Haskell en Clean, maar met name ook omdat de methode die ik hier toepas (foldr dus) algemeen wel als een methode wordt gezien die zuinig omgaat met geheugen en snelle uitvoer oplevert. Het enorme verschil wat jij noemt (kbs versus tientallen mbs) vind ik daarom wel erg opvallend.
Zoals ik al eerder zei, is foldl tail recursive in tegenstelling tot foldr. Daarom gebruikt foldr normaal gesproken een hoeveelheid geheugen relatief aan de lengte van de lijst waarop 'ie werkt en foldl constant geheugen. Ik weet niet hoe foldl en foldr in Haskell gedefineerd zijn, maar in Clean gaat het zo:
code:
1
2
3
4
5
6
7
8
9
| foldl op r l :== foldl r l
where
foldl r [] = r
foldl r [a:x] = foldl (op r a) x
_
foldr op r l :== foldr l
where
foldr [] = r
foldr [a:x] = op a (foldr x) |
Door de ':==' zijn het macro's, zodat de compiler ze zal inlinen. Hier is wel goed te zien dat foldr eerst de hele gereduceerde expressie moet uitvinden, voordat 'op' voor de eerste keer toegepast kan worden. Het resultaat is dat voor (bijvoorbeeld) 'foldr (+) 0 [1,2,3,4,5]' eerst naar '0 + (1 + (2 + (3 + (4 + (5) ) ) )' uitgewerkt moet worden, waarna die expressie wordt uitgevoerd.
Stapsgewijs ziet dat er zo uit:
code:
1
2
3
4
5
6
7
8
9
10
11
12
| 1. foldr 0 [1,2,3,4,5]
2. 1 + (foldr 0 [2,3,4,5])
3. 1 + (2 + (foldr 0 [3,4,5]))
4. 1 + (2 + (3 + (foldr 0 [4,5]))
5. 1 + (2 + (3 + (4 + (foldr 0 [5])))
6. 1 + (2 + (3 + (4 + (foldr 0 [5]))))
7. 1 + (2 + (3 + (4 + (5+ 0)))))
8. 1 + (2 + (3 + (4 + 5))))
9. 1 + (2 + (3 + 9))
10. 1 + (2 + (12))
11. 1 + (14)
12. 15 |
Duidelijk is dat bij stap 7 alle tussenresultaten in het geheugen staan.
foldl voert eerst de operator uit en dan pas de recursiestap, die direct de huidige functieaanroep kan vervangen (dat heet dus tail recursive). Het resultaat is dat de evaluatie van 'foldl (+) 0 [1,2,3,4,5]' als volgt gaat:
code:
1
2
3
4
5
6
7
8
9
10
11
12
| 1. foldl 0 [1,2,3,4,5]
2. foldl (0+1) [2,3,4,5]
3. foldl 1 [2,3,4,5]
4. foldl (1+2) [3,4,5]
5. foldl 3 [3,4,5]
6. foldl (3+3) [4,5]
7. foldl 6 [4,5]
8. foldl (6+4) [5]
9. foldl 10 [5]
10. foldl (10+5) []
11. fold 15 []
12. 15 |
Vergeef me als ik haakjes ben vergeten of tussenstappen heb gemist, ik vind het voorbeeld zo wel lang genoeg.

Ik denk dat zo wel duidelijk is dat foldr op deze manier niet echt praktisch is. Het zou kunnen dat Haskell foldr anders gedefinieerd heeft (en echt aan 't einde van de lijst begint) maar dat zou ik niet durven zeggen.
Kan het zijn dat de Clean library hele specifieke tweaks bevat die jouw implementatie hier veel beter laten functioneren?
Vanuit de implemenatie van de standaard library verbaast het me niets dat foldr minder efficient werkt. Het aantal reductiestappen is wel gelijk, maar doordat veel meer geheugen wordt gebruikt, valt het voordeel van caching e.d. weg. Ook het alloceren van zo veel geheugen kost natuurlijk tijd (het garbage collecten heb ik niet meegeteld).