Cycle.js approach to handling IO looks similar to how it was done in earlier FRP implementations. That seems to be an independent discovery, and that's always even better.
Yampa provides a function reactimate
:
:: IO a -- ^ IO initialization action
-> (Bool -> IO (DTime, Maybe a)) -- ^ IO input sensing action
-> (Bool -> b -> IO Bool) -- ^ IO actuaction (output processing) action
-> SF a b -- ^ Signal function
-> IO ()
First parameter is IO a
, initialization action is an additional "init" function which Cycle doesn't have/need.
Second two parameters are important, they could be combined into a cycle driver:
Starting with input sensing action removing unrelated parts it's IO (Maybe a)
which is a side-effecty function that 'Maybe' provides some output. In Cycle this would be a 'source' part of driver which is an input of main
.
Something that would return you the events of user interaction.
The output actuation action is essentially of a type b -> IO ()
what it does is that it takes the
output and does something with it possibly producing side-effects.
It can render it to the screen, play with virtual-dom, whatever.
In Cycle this is handled by a 'sink' part of driver which is output of main
.
The last parameter SF a b
(note that a
and b
correspond to the same things as in previous type signatures).
That is a "Signal Function" which semantically is Signal a -> Signal b
.
In case of Cycle, Observable
is used in place of Signal
.
Cycle's main
is basically the same signal/observable function which has a type Observable a -> Observable b
(Cycle's version also provides nicer management of multiple observables, which could be implemented on top of that).
And note, that a
comes from the input sensing function, and b
goes to output processing function.
So alltogether, I would say that Cycle's run
could be implemented on top of reactimate
(with some hacks probably).
The idea is that run
provides the same essense of interaction with user, but wraps it in a nicer API @staltz.
Note how cluttered are input/output actions API, Cycle.js cycles them together in a driver and leaves most of the lifting to RxJS.
The greatest thing is that main
/SF a b
part, is exactly identical and that's the most important part.
I also noted how similar the terminology is, particularly "senses" and "actuations/responses". That's a great independent discovery.
I've recently made a simple/incomplete reimplementation of Yampa in Purescript, and it features an example of simple game. Yampy-cube play here: https://github.com/zudov/purescript-miniyampa/blob/master/examples/yampy-cube/src/Main.purs
If you look at the source, it's very pure and functional except for the one part:
runDrawingApp sf ctx inputRef inputMod = do
rh <- reactInit (readRef inputRef) (\_ _ -> render) sf
lastInteractionRef <- do Milliseconds t0 <- nowEpochMilliseconds
newRef t0
let step = do
Milliseconds now <- nowEpochMilliseconds
lastInteraction <- readRef lastInteractionRef
let dt = now - lastInteraction
writeRef lastInteractionRef now
input <- readRef inputRef
modifyRef inputRef inputMod
react rh $ Tuple (dt / 1000.0) $ Just input
requestAnimationFrame step
requestAnimationFrame step
main = do
Just canvas <- C.getCanvasElementById "canvas"
ctx <- C.getContext2D canvas
canvasJQ <- JQ.select "canvas"
inputRef <- newRef { increment: NoEvent }
JQ.on "click" (\_ _ -> modifyRef inputRef (_ { increment = Event 1 })) canvasJQ
runDrawingApp (game >>> updated >>^ map render) ctx inputRef (_ { increment = NoEvent })
That's the only imperative part which actually just sets up inputs/outputs and feeds them to the processing loop. I have a feeling that Cycle could clean up that clutter greatly. I should try to bind them together and update that example.