Pas vraiment un problème, en fait. Une solution complète utiliserait probablement les monades Reader, IO et State, plus Iteratee et les objectifs, mais voici une version plus simple :
case class State(dailyHigh: Int = 0)
object Main {
type Event = (State => State)
def mainLoop(currState: State, events: Stream[Event]): State =
if (events.nonEmpty) {
val newState = events.head(currState)
mainLoop(newState, events.tail)
} else currState
def onTrade(price: Int): Event = (s: State) =>
if (price > s.dailyHigh) s.copy(dailyHigh = price) else s
def main(args: Array[String]) {
val events = onTrade(5) #:: onTrade(2) #:: onTrade(10) #:: onTrade(5) #:: Stream.empty
val finalState = mainLoop(State(), events)
println(finalState)
}
}
Regarde, maman, pas de vars !
L'État peut devenir assez complexe, bien sûr, mais c'est là que les objectifs entrent en jeu. Avec les lentilles, il est assez facile de consulter et de modifier (copier avec une nouvelle valeur) des structures de données arbitrairement complexes.
L'utilisation d'itérations est naturelle pour les événements -- dans un sens très simple, "onTrade" devient une itération qui est invoquée par un énumérateur (la chose qui "génère" les événements) avec chaque événement si elle est composée à partir d'une fonction partielle, vous pouvez toutes les plier en une seule fonction partielle.
Une autre solution consiste à combiner les monades d'état avec les monades d'entrée/sortie sur les for-compréhensions.
Enfin, il y a l'option des continuations. Si un traitement nécessite la réception d'une chaîne d'événements, alors le résultat de chaque événement peut être une continuation, et la continuation elle-même devient une partie de l'état.