I wanted to give FRP some time, and yesterday I finally removed the bullet and went using Netwire 5 to start (a rather arbitrary choice in itself, but I need to start somewhere!). I managed to move on to “code that works,” but I noticed a couple of patterns that I'm not sure are part of how the library is expected to be used or they are a symptom, something is wrong.
I started with this tutorial , which was enough to make me work and work quite easily - now I have a rotating cube, controlled by a simple “increasing number” wire:
spin :: (HasTime ts, Monad m) => Wire sema GL.GLfloat spin = integral 0 . 5
and the application will stop working when you press "Esc", using the wires included in netwire-input-glfw :
shouldQuit :: (Monoid e, Functor m, Monad m) => Wire se (GLFWInputT m) aa shouldQuit = keyPressed GLFW.Key'Escape
An important difference between the two is that spin never suppresses - it should always return some value, and shouldQuit constantly blocking; until the key is pressed, in this case I exit the application.
What bothers me is how I have to use these wires. Now it looks something like this:
(wt', spinWire') <- stepWire spinWire st $ Right undefined ((qt', quitWire'), inp'') <- runStateT (stepWire quitWire st $ Right undefined) inp' case (qt', wt') of (Right _, _) -> return () (_, Left _) -> return () -- ??? (_, Right x) -> --do things, render, recurse into next frame
There are two things about this template that make me feel uncomfortable. Firstly, the fact that I pass Right undefined for both stepWire calls. I think (if my understanding is correct) that this parameter is designed to send events to the wire, and that since my wires do not use any events, it is "safe", but it feels bad ( EDIT strong> possibly "events" - this is the wrong word here - the tutorial describes it as "blocking values", but the point is still there - I'm never going to block and not use the e parameter anywhere on my wire). I looked to see if there was a stepWire version for a situation where you know that you never had an event and you wouldn’t respond to it, even if you had one, but he couldn’t see it. I tried making wire an e () parameter, and then skipping Right () everywhere that feels less dirty than undefined , but still doesn't quite seem to be my intention.
Similarly, the return value is also equal to Either . This is perfect for shouldQuit wires, but note that I need to map the pattern to wt' , the output of the spin wire. I really don't know what this would mean for this to discourage, so I just return () , but I can imagine that it becomes cumbersome as the number of wires increases, and again, it just doesn't seem like a representative of my intent - to have a wire that never suppresses and on which I can always rely on to keep the next value.
So, although I have some code that works, I’m left with an awkward feeling that I am “doing it wrong” anyway, since Netwire 5 is rather newer, it’s hard to find examples of “idiomatic” code that I can check and see am I not close to the sign. Is this how the library is intended to be used or am I missing something?
EDIT : I was able to solve the second problem that I mention (matching patterns in Either result of spin ) by combining spin and shouldQuit into one Wire :
shouldContinuePlaying :: (Monoid e, Functor m, Monad m) => Wire se (GLFWInputT m) aa shouldContinuePlaying = keyNotPressed GLFW.Key'Escape game :: (HasTime ts, Monoid e, Functor m, Monad m) => Wire se (GLFWInputT m) a GL.GLfloat game = spin . shouldContinuePlaying
The step along this wire gives me a much more reasonable return value - if it is Left , I can leave, otherwise I have some data to work with. It also indicates a greater degree of compatibility than my original method.
I still need to pass Right undefined as input to this new wire. Admittedly, there is only one of them now, but I'm still not sure if this is the right approach.