Netwire
From HaskellWiki
(→Predefined wires) |
m (→Predefined wires) |
||
| (3 intermediate revisions not shown.) | |||
| Line 115: | Line 115: | ||
| - | == Predefined wires == | + | == Writing wires == |
| + | |||
| + | === Predefined wires === | ||
There are numerous predefined wires, which you can compose using the arrow interface. We will practice that with three very simple predefined wires (the type signatures are simplified for the sake of learning): | There are numerous predefined wires, which you can compose using the arrow interface. We will practice that with three very simple predefined wires (the type signatures are simplified for the sake of learning): | ||
| Line 166: | Line 168: | ||
</haskell> | </haskell> | ||
| - | In applications it is common to write wires that ignore their input. For those wires you should make the input type fully polymorphic. Running this wire produces: | + | In applications it is common to write wires that ignore their input. For those wires you should make the input type fully polymorphic to indicate this. Running this wire produces: |
<haskell> | <haskell> | ||
| + | stepWireM system () | ||
| + | |||
1st instant: Right "10 20" | 1st instant: Right "10 20" | ||
2nd instant: Right "11 21" | 2nd instant: Right "11 21" | ||
3rd instant: Right "12 22" | 3rd instant: Right "12 22" | ||
| + | </haskell> | ||
| + | |||
| + | Note: You can use the ''testWireM'' function with this wire. The following action will run the wire continuously printing its result at every 1000th instant: | ||
| + | |||
| + | <haskell> | ||
| + | main :: IO () | ||
| + | main = testWireM 1000 (return ()) system | ||
| + | </haskell> | ||
| + | |||
| + | In the FRP context we often talk about ''signals''. Particularly in the context of ''arrowized'' FRP (AFRP) like Netwire we talk about ''signal networks'' and signals passing through them. The ''system'' wire is your first signal network. It ignores its input signal and passes the signal <hask>()</hask> to the two counters (which ignore their input signals, too). It takes the output signals <hask>c1</hask> and <hask>c2</hask> and makes a formatted string out of them. Finally this string is passed to the <hask>identity</hask> wire. This is the last wire in the signal network ''system'', so its output signal is the output signal of ''system''. As a side note the ''identity'' wire behaves like ''returnA''. | ||
| + | |||
| + | The main feature to note here is that all of the subwires in the composition evolve individually. So in the second instant, each of the two counters will have gone up by one. This alone gives you a powerful abstraction for stateful computations. The equivalent when using a state monad or mutable variables would be to have a global state value with two counter values. By having time-varying functions you can have something called ''local state''. Each of the two counters (or as many as you use) have their own individual local state, which is the current counter value. This is way more convenient and composable than a state monad or other imperative state abstractions. | ||
| + | |||
| + | === Choice === | ||
| + | |||
| + | In traditional AFRP solutions like Yampa the path of a signal is fully determined by the structure of the signal network. In Netwire a signal can choose one of multiple paths by using the <hask>case</hask> and <hask>if</hask> constructs: | ||
| + | |||
| + | <haskell> | ||
| + | system = | ||
| + | proc _ -> do | ||
| + | c1 <- countFrom 10 -< () | ||
| + | if even c1 | ||
| + | then returnA -< "We don't want even c1" | ||
| + | else do | ||
| + | c2 <- countFrom 20 -< () | ||
| + | returnA -< printf "%d %d" (c1 :: Int) (c2 :: Int) | ||
| + | </haskell> | ||
| + | |||
| + | If the <hask>c1</hask> signal is even, then the wire outputs the string "We don't want even c1". Otherwise it takes the second path. Here it is important to know that the second counter will be suspended, when <hask>c1</hask> is even, because the <hask>else</hask> branch is not reached. A wire can only evolve, when it is actually reached. So in this example <hask>c2</hask> will run at half the speed of <hask>c1</hask> and the output will look like: | ||
| + | |||
| + | <haskell> | ||
| + | 1st instant: "We don't want even c1" | ||
| + | 2nd instant: "11 20" | ||
| + | 3rd instant: "We don't want even c1" | ||
| + | 4th instant: "13 21" | ||
| + | 5th instant: "We don't want even c1" | ||
| + | 6th instant: "15 22" | ||
| + | 7th instant: "We don't want even c1" | ||
</haskell> | </haskell> | ||
[[Category:FRP]] | [[Category:FRP]] | ||
Current revision
Netwire is a library for functional reactive programming, which uses the concept of arrows for modelling an embedded domain-specific language. This language lets you express reactive systems, which means systems that change over time. It shares the basic concept with Yampa and its fork Animas, but it is itself not a fork and has many additional features.
This wiki page corresponds to Netwire version 3 and is currently a work in progress.
Contents |
1 Features
Here is a list of some of the features of Netwire:
- arrow interface (or optionally an applicative interface),
- signal inhibition (ArrowZero / Alternative),
- signal selection (ArrowPlus / Alternative),
- self-adjusting wires (ArrowChoice),
- rich set of event wires,
- signal analysis wires (average, peak, etc.),
- effectful wires.
2 Basics
The Netwire library is based around a data type calledimport Control.Wire data Wire e (>~) a b
comp :: Wire e (>~) a b
a >~ Either e b
2.1 The inhibition monoid
The2.2 Base arrows
Thetype MyWire = Wire () (Kleisli IO)
3 Running wires
For running a wire you can use the stepping functions available in thestepWireM :: Monad m => Wire e (Kleisli m) a b -> a -> m (Either e b, Wire e (Kleisli m) a b)
stepWireM :: MyWire a b -> a -> IO (Either () b, MyWire a b)
system :: MyWire Int String system = {- ... -} main :: IO () main = loop system where loop :: MyWire Int String -> IO () loop w' = do (mx, w) <- stepWireM w' 15 {- ... do something with mx ... -} loop w -- loop with the new wire.
Note: Even though the FRP idea suggests it, there is no reason to run wires continuously or even regularly. You can totally have an instant depending on user input, a GUI event or network traffic, so instants can be minutes apart.
3.1 Testing wires
There is a convenient function for testing wires, which does all the plumbing for you. It's calledtestWireM :: (Show e, MonadIO m) => Int -> m a -> Wire e (Kleisli m) a String -> m ()
main :: IO () main = testWireM 1000 (return 15) system
4 Writing wires
4.1 Predefined wires
There are numerous predefined wires, which you can compose using the arrow interface. We will practice that with three very simple predefined wires (the type signatures are simplified for the sake of learning):
constant :: b -> Wire e (>~) a b identity :: Wire e (>~) b b countFrom :: Enum b => b -> Wire e (>~) a b
stepWireM (constant 15) inp -> (Right 15, constant 15)
stepWireM identity inp -> (Right inp, identity)
Both identity and constant wires are examples of stateless wires. They don't change over time. You can see this in the stepping examples above. They always return themselves for the next instant.
The countFrom function takes a starting value and returns a wire that returns sequential values instant by instant. This is the first example of a stateful wire, because it changes over time:
stepWireM (countFrom 15) inp -> (Right 15, countFrom 16) stepWireM (countFrom 16) inp -> (Right 16, countFrom 17)
4.2 Composing wires
The main feature of wires is that you can compose them using the arrow interface. There is a rich set of ways for composing, and you will want to use arrow notation for your convenience:
system :: MyWire a String system = proc _ -> do c1 <- countFrom 10 -< () c2 <- countFrom 20 -< () identity -< printf "%d %d" (c1 :: Int) (c2 :: Int)
In applications it is common to write wires that ignore their input. For those wires you should make the input type fully polymorphic to indicate this. Running this wire produces:
stepWireM system () 1st instant: Right "10 20" 2nd instant: Right "11 21" 3rd instant: Right "12 22"
Note: You can use the testWireM function with this wire. The following action will run the wire continuously printing its result at every 1000th instant:
main :: IO () main = testWireM 1000 (return ()) system
The main feature to note here is that all of the subwires in the composition evolve individually. So in the second instant, each of the two counters will have gone up by one. This alone gives you a powerful abstraction for stateful computations. The equivalent when using a state monad or mutable variables would be to have a global state value with two counter values. By having time-varying functions you can have something called local state. Each of the two counters (or as many as you use) have their own individual local state, which is the current counter value. This is way more convenient and composable than a state monad or other imperative state abstractions.
4.3 Choice
In traditional AFRP solutions like Yampa the path of a signal is fully determined by the structure of the signal network. In Netwire a signal can choose one of multiple paths by using thesystem = proc _ -> do c1 <- countFrom 10 -< () if even c1 then returnA -< "We don't want even c1" else do c2 <- countFrom 20 -< () returnA -< printf "%d %d" (c1 :: Int) (c2 :: Int)
1st instant: "We don't want even c1" 2nd instant: "11 20" 3rd instant: "We don't want even c1" 4th instant: "13 21" 5th instant: "We don't want even c1" 6th instant: "15 22" 7th instant: "We don't want even c1"
