Getting input into Netwire programs
Asked Answered
U

2

11

I'm getting started with Netwire version 5.

I have no problem writing all the wires I want to transform my inputs into my outputs.

Now the time has come to write the IO wrapper to tie in my real-world inputs, and I am a bit confused.

Am I supposed to create a custom session type for the s parameter of Wire s e m a b and embed my sensor values in there?

If so, I have these questions:

  1. What's up with the Monoid s context of class (Monoid s, Real t) => HasTime t s | s -> t? What is it used for?
  2. I was thinking of tacking on a Map String Double with my sensor readings, but how should my monoid crunch the dictionaries? Should it be left-biased? Right-biased? None of the above?

If not, what am I supposed to do? I want to end up with wires of the form Wire s InhibitionReason Identity () Double for some s, representing my input.

It's my understanding that I don't want or need to use the monadic m parameter of Wire for this purpose, allowing the wires themselves to be pure and confining the IO to the code that steps through the top-level wire(s). Is this incorrect?

Unpremeditated answered 4/4, 2014 at 0:45 Comment(2)
There's a monoid instance for Map that's left biased. And I'd actually think making the monadic context here a reader monad and stuffing your sensor information in there would be reasonable here. This doesn't really impact the place where you step your wires because you can just runReader (or runReaderT if you wanna stuff in even more stuff).Kimberykimble
Tutorials/examples out there suggest to use IO actions in inner wires to get sensor data. e.g. use mkGen_ with getKey in it. I would be interested in generalizing the question to: "What are advantages and disadvantages of allowing IO actions inside wires, as compared to feeding all IO data as input to the outermost wire?"Emulsifier
C
2

The simplest way to put data into a Wire s e m a b is via the input a. It's possible, through the use of WPure or WGen to get data out of the state delta s or the underlying Monad m, but these take us further away from the main abstractions. The main abstractions are Arrow and Category, which only know about a b, and not about s e m.

Here's an example of a very simple program, providing input as the input a. double is the outermost wire of the program. repl is a small read-eval-print loop that calls stepWire to run the wire.

import FRP.Netwire
import Control.Wire.Core

import Prelude hiding (id, (.))

double :: Arrow a => a [x] [x]
double = arr (\xs -> xs ++ xs)

repl :: Wire (Timed Int ()) e IO String String -> IO ()
repl w = do
    a <- getLine
    (eb, w') <- stepWire w (Timed 1 ()) (Right a)
    putStrLn . either (const "Inhibited") id $ eb
    repl w'

main = repl double

Notice that we pass in the time difference to stepWire, not the total elapsed time. We can check that this is the correct thing to do by running a different top-level wire.

timeString :: (HasTime t s, Show t, Monad m) => Wire s e m a String
timeString = arr show . time

main = repl timeString 

Which has the desired output:

a
1
b
2
c
3
Cyclopean answered 3/9, 2014 at 4:16 Comment(0)
P
1

I just solved this in an Arrow way, so this might be more composible. You can read my posts if you like. Kleisli Arrow in Netwire 5? and Console interactivity in Netwire?. The second post has a complete interactive program

First, you need this to lift Kleisli functions (That is, anything a -> m b):

mkKleisli :: (Monad m, Monoid e) => (a -> m b) -> Wire s e m a b
mkKleisli f = mkGen_ $ \a -> liftM Right $ f a

Then, assuming you want to get characters from terminal, you can lift hGetChar by doing this:

inputWire :: Wire s () IO () Char
inputWire = mkKleisli $ \_ -> hGetChar stdin

I haven't tested this runWire function (I just stripped code off from my previous posts), but it should run your wires:

runWire :: (Monad m) => Session m s -> Wire s e m () () -> m ()
runWire s w = do
  (ds, s') <- stepSession s
  -- | You don't really care about the () returned
  (_, w') <- stepWire w ds (Right ())
  runWire s' w'

You can compose the input wire wherever you like like any other Wires or Arrows. In my example, I did this (don't just copy, other parts of the program are different):

mainWire = proc _ -> do 
  c <- inputWire -< ()
  q <- quitWire -< c
  outputWire -< c
  returnA -< q

Or, one-liner:

mainWire = inputWire >>> (quitWire &&& outputWire) >>> arr (\(q,_) -> q)
Phlebotomize answered 29/9, 2015 at 22:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.