Reputation: 3078
I'm trying to understand the practical difference between a FRP graph and a State Machine with lenses- specifically for something like a game loop where the entire state is re-drawn every tick.
Using javascript syntax, the following implementations would both essentially work:
Option 1: State Machine w/ Lenses
//Using Sanctuary and partial.lenses (or Ramda) primitives
//Each update takes the state, modifies it with a lens, and returns it
let state = initialValues;
eventSource.addEventListener(update, () => {
state = S.pipe([
updateCharacter,
updateBackground,
])
(state) //the first call has the initial settings
render(state);
});
Option 2: FRP
//Using Sodium primitives
//It's possible this isn't the best way to structure it, feel free to advise
cCharacter = sUpdate.accum(initialCharacter, updateCharacter)
cBackground = sUpdate.accum(initialBackground, updateBackground)
cState = cCharacter.lift(cBackground, mergeGameObjects)
cState.listen(render)
I see that Option 1
allows any update to get or set data anywhere in the game state, however all the cells/behaviors in Option 2
could be adjusted to be of type GameState
and then the same thing applies. If this were the case, then I'm really confused about the difference since that would then just boil down to:
cGameState = sUpdate
.accum(initialGameState, S.pipe(...updates))
.listen(render)
And then they're really very equivalent...
Another way to achieve that goal would be to store all the Cells in some global reference, and then any other cell could sample them for reading. New updates could be propagated for communicating. That solution also feels quite similar to Option 1 at the end of the day.
Is there a way to structure the FRP graph in such a way that it offers clear advantages over the event-driven state machine, in this scenario?
Upvotes: 3
Views: 708
Reputation: 11064
I'm not quite sure what your question is, also because you keep changing the second example in your explanatory text.
In any case, the key benefit of the FRP approach — as I see it — is the following: The game state depends on many things, but they are all listed explicitly on the right-hand side of the definition of cGameState
.
In contrast, in the imperative style, you have a global variable state
which may or may not be changed by code that is not shown in the snippet you just presented. For all I know, the next line could be
eventSource2.addEventListener(update, () => { state = state + 1; })
and the game state suddenly depends on a second event source, a fact that is not apparent from the snippet you showed. This cannot happen in the FRP example: All dependencies of cGameState
are explicit on the right-hand side. (They may be very complicated, sure, but at least they are explicit.)
Upvotes: 3