Un pattern frequente nel mio codice Haskell è la ricorsione basata sull'elemento per la trasformazione di una lista con uno stato portato generato usando i dati nella lista. Di solito, questo appare in questo modo:
doSomething :: (SomeA a, SomeB b) => [a] -> [b]
doSomething xs = doSomethingWithState xs []
where
doSomethingWithState [] _ = []
doSomethingWithState (x:xs) state
| someTest x state = someChange x : (doSomethingWithState xs newState)
| otherwise = someOtherChange x : (doSomethingWithState xs newState)
Ad esempio, supponiamo di voler contare quanti di ciascun elemento appaiono in un elenco, trasformando qualcosa come [1, 3, 3, 3, 4, 7, 7, 8, 8, 9]
in [(9,1),(8,2),(7,2),(4,1),(3,3),(1,1)]
.
Probabilmente farei qualcosa del tipo:
import Data.List
import Data.Maybe
counts :: (Eq a) => [a] -> [(a, Int)]
counts xs = countsWithState xs []
where
countsWithState [] state = state -- End of list, stop here
countsWithState (x:xs) state = countsWithState xs $ transformState state x
transformState state x -- To get the state...
| isNothing $ lookup x state = (x, 1) : state -- Add new elem if new
| otherwise = incrElem x state -- Increment elem if not
incrElem x [] = [] -- Should never be reached
incrElem x ((index, value):elems) -- Searching through list...
| index == x = (index, (value+1)) : elems -- Increment if found
| otherwise = (index, value) : incrElem x elems -- Try next if not
In un esempio molto più semplice ma molto simile, se provassi a mantenere la media corrente di tutti gli elementi in una lista, trasformando qualcosa come [1, 7, 4, 18, 7, 1, 8, 2, 8, 6, 18, 12]
in [1.0, 4.0, 4.0, 7.5, 7.4, 6.33..., 5.57..., 6.0, 6.22..., 6.2, 7.27..., 7.66...]
dove ogni elemento nella lista di output è la media di quello elemento e tutti gli elementi precedenti nella lista di input, potrei fare qualcosa di simile a questo:
runningAvg :: (Fractional a) => [a] -> [a]
runningAvg xs = runningAvgWithState xs 0 1
where
runningAvgWithState [] _ _ = []
runningAvgWithState (x:xs) currentSum currentElems
= (currentSum + x) / currentElems
: runningAvgWithState xs (currentSum + x) (currentElems + 1)
Si noti che il modello è lo stesso. Prendi una funzione ricorsiva di una lista, definiscila in termini di una versione modificata nascosta con stato aggiunto, e con ogni round trasforma lo stato e genera i risultati calcolati secondo necessità. Questo schema emerge continuamente nel mio codice Haskell .
Esiste un modo più naturale di implementare questo tipo di comportamento, senza una più complicata funzione xWithState
che esegue lo spettacolo e aggiungendo inutili verbosità e complessità?