Proper Use of Netwire (5)

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.

+5
source share
1 answer

At the very top level of your program, you will have a wire having the (abbreviated) type Wire ab . This should be passed something of type a and it will return something of type b every time you take a step. For example, both a and b may be some WorldState for the game, or maybe [RigidBody] for the physical simulator. In my opinion, it is normal to go Right undefined at the top level.

In doing so, you ignore the important Alternative Wire ab instance for input wires. It provides the <|> operator, which works very nicely:

Suppose we have two wires:

 w1 :: Wire ab w2 :: Wire ab 

If w1 suppresses, then

 w1 <|> w2 == w2 

If w1 does not block, then

 w1 <|> w2 == w1 

This means that w1 <|> w2 will only suppress if both w1 and w2 prohibited. This is great, it means that we can do things like:

 spin :: (HasTime ts, Monad m) => Wire sema GL.GLfloat spin = integral 0 . (10 . keyPressed GLFW.Key'F <|> 5) 

When you press F , you will rotate twice as fast!

If you want to change the semantics of a wire after pressing a button, you should be a little more creative, but not very creative. If your wire behaves differently, it means that you are making some kind of switch. The documentation for the switches basically requires following the types .

Here is a wire that will act as a personal wire until you press this key, and then will be blocked forever:

 trigger :: GLFW.Key -> GameWire aa trigger key = rSwitch mkId . (mkId &&& ((now . pure mkEmpty . keyPressed key) <|> never)) 

With this, you can do cool things like:

 spin :: (HasTime ts, Monad m) => Wire sema GL.GLfloat spin = integral 0 . spinSpeed where spinSpeed = 5 . trigger GLFW.Key'F --> -5 . trigger GLFW.Key'F --> spinSpeed 

This will toggle the counter between going back and forth whenever you press F

+1
source

Source: https://habr.com/ru/post/1213661/


All Articles