# Rhine

### From HaskellWiki

m (→Clock safety ) |
|||

(One intermediate revision by one user not shown) | |||

Line 19: | Line 19: | ||

In Rhine, you can annotate each (synchronous) signal function with a clock type. This clock type gives ''clock safety'' since throughout the framework it is ensured that every signal function is run at the speed of this clock. | In Rhine, you can annotate each (synchronous) signal function with a clock type. This clock type gives ''clock safety'' since throughout the framework it is ensured that every signal function is run at the speed of this clock. | ||

+ | |||

+ | A 'running clock' is an effectful stream of time stamps. (The side effect can include polling the system time, a device, or waiting/blocking until a certain time.) The clock is said to tick whenever a time stamp is emitted. | ||

Rhine has a number of ready-to-use clocks, for example fixed-rate clocks like <haskell>Millisecond n</haskell> where <haskell>n</haskell> is a type-level natural number. | Rhine has a number of ready-to-use clocks, for example fixed-rate clocks like <haskell>Millisecond n</haskell> where <haskell>n</haskell> is a type-level natural number. | ||

+ | |||

+ | ==== Events ==== | ||

+ | |||

+ | Event sources are also clocks in a natural sense that tick every time an event occurs. | ||

=== Signal functions === | === Signal functions === | ||

Line 35: | Line 41: | ||

</haskell> | </haskell> | ||

− | + | Here, <hask>m</hask> is an arbitrary monad and <hask>cl</hask> is an arbitrary clock type. <hask>a</hask> is the input data type (in this case it is arbitrary because any input is discarded), and <hask>Pos</hask> is the output data type. | |

− | Being based on | + | Being based on [https://github.com/ivanperez-keera/dunai Dunai], |

signal functions can also perform side effects in a monad <hask>m</hask>: | signal functions can also perform side effects in a monad <hask>m</hask>: | ||

Line 80: | Line 86: | ||

<code> | <code> | ||

− | error: | + | error: * Couldn't match type `500' with `1000' |

− | + | ||

[...] | [...] | ||

</code> | </code> | ||

+ | |||

+ | In general, clock type errors will occur when trying to compose Rhine components at non-matching clocks. This feature is called 'clock-safety'. Subsystems on different clocks have to communicate over explicitly specified resampling buffers, coordinated by explicit schedules (see sections below). | ||

+ | |||

+ | ==== Behaviours and clock-polymorphism ==== | ||

+ | |||

+ | Signal functions can be polymorphic in the clock. In that case, they are called <hask>Behaviour</hask>s, since they model the original FRP idea of a value (or function) varying with time, oblivious of the sampling/clocking strategy. | ||

=== Schedules === | === Schedules === | ||

− | '' | + | A schedule for two clocks <hask>cl1</hask> and <hask>cl2</hask> is a universal clock such that <hask>cl1</hask> and <hask>cl2</hask> are subclocks. The schedule ticks exactly whenever either <hask>cl1</hask> or <hask>cl2</hask> would tick. |

+ | |||

+ | Rhine has a number of predefined schedules, so you rarely need to implement your own. For example, you can use concurrency and let the framework run the two clocks in separate background threads, while all concurrent communication is encapsulated and hidden from the library user. For certain clocks, such as fixed-step clocks, it is also possible to schedule them 'deterministically', which is very useful when e.g. audio and video sampling ratios need to be kept stable. | ||

=== Resampling buffers === | === Resampling buffers === | ||

− | '' | + | A resampling buffer is the fundamental asynchronous data component in Rhine. It accepts <hask>put</hask> and <hask>get</hask> calls that put data into, and get data from, the buffer. Usually, these methods need not be called explicitly. Instead, resampling buffers connect synchronous signal functions that work at different rates. |

+ | |||

+ | The Rhine library implements standard buffering and resampling techniques such as FIFO queues, linear/cubic/sinc interpolation, first order holds and others. Some buffers, like fixed-rate up-/downsamplers, are annotated with clock types in order to be used only at the correct rate. Others are clock-polymorphic. | ||

+ | |||

+ | === Main loops === | ||

+ | |||

+ | Signal functions and resampling buffers are composed to 'signal networks'. In turn, signal networks together with compatible clocks form the main high-level components, called <hask>Rhine</hask>s themselves. | ||

+ | The library has several combinators that allow for easy creation of <hask>Rhine</hask>s from basic components. | ||

− | + | A 'closed' <hask>Rhine</hask>, i.e. one that has the trivial input and output type <hask>()</hask>, is a main loop. It is run by starting its clock, feeding the time stamps into the signal network and let it perform its side effects. | |

− | + | All this is done automatically by the framework without inserting any additional side effects. This makes Rhine suitable for reproducible testing, since it hides no IO under the hood. | |

== Development == | == Development == |

## Latest revision as of 21:07, 26 September 2018

Rhine is an arrowized FRP library with **type level clocks**. It is designed with multi-rate FRP in mind, combining synchronous arrowized FRP features (as in Yampa) with asynchronous building blocks.

## Contents |

## [edit] 1 Building blocks in Rhine

There are two kinds of separation of concerns: First, temporal aspects (clocking/sampling) are separated from dataflow aspects. Second, synchronous components are separated from asynchronous ones. The situation is summarised in this table:

Time | Data | |
---|---|---|

Synchronous | Clocks | Clocked signal functions |

Asynchronous | Schedules | Resampling buffers |

### [edit] 1.1 Clocks

In Rhine, you can annotate each (synchronous) signal function with a clock type. This clock type gives *clock safety* since throughout the framework it is ensured that every signal function is run at the speed of this clock.

A 'running clock' is an effectful stream of time stamps. (The side effect can include polling the system time, a device, or waiting/blocking until a certain time.) The clock is said to tick whenever a time stamp is emitted.

Rhine has a number of ready-to-use clocks, for example fixed-rate clocks likeMillisecond n

n

#### [edit] 1.1.1 Events

Event sources are also clocks in a natural sense that tick every time an event occurs.

### [edit] 1.2 Signal functions

Signal functions are aware of time and internal state. Standard arrowized FRP constructions are implemented in Rhine:

type Pos = (Double, Double, Double) freeFallFrom10Meters :: ClSF m cl a Pos freeFallFrom10Meters = arr (const (0,0,-9.81)) >>> integral >>> integralFrom (0,0,10)

Being based on Dunai,

signal functions can also perform side effects in a monadexitWhenNegative :: ClSF (ExceptT () m) cl Pos () exitWhenNegative = throwOn' <<< arr (\(_, _, z) -> (z < 0, ()))

*control flow*!

freeFallThenOnTheGround :: ClSF m cl a Pos freeFallThenOnTheGround = safely $ do try $ freeFallFrom10Meters >>> (id &&& exitWhenNegative) >>> arr fst safe $ arr $ const (0, 0, 0)

printHeight :: ClSF IO cl a () printHeight = freeFallThenOnTheGround >>> arrMCl print

Rhine does not hide any side effects under the hood. All effects that are used are visible in the type.

#### [edit] 1.2.1 Clock safety

Signal functions are "clocked" and can only be composed synchronously when their clocks match. The following will not typecheck:

doubleAt1Hz :: ClSF m (Millisecond 1000) Integer Integer doubleAt1Hz = arr (* 2) doubleAt2Hz :: ClSF m (Millisecond 500) Integer Integer doubleAt2Hz = arr (* 2) quadrupleAtNonsensicalRate = doubleAt1Hz >>> doubleAt2Hz

```
error: * Couldn't match type `500' with `1000'
[...]
```

In general, clock type errors will occur when trying to compose Rhine components at non-matching clocks. This feature is called 'clock-safety'. Subsystems on different clocks have to communicate over explicitly specified resampling buffers, coordinated by explicit schedules (see sections below).

#### [edit] 1.2.2 Behaviours and clock-polymorphism

Signal functions can be polymorphic in the clock. In that case, they are called### [edit] 1.3 Schedules

A schedule for two clocksRhine has a number of predefined schedules, so you rarely need to implement your own. For example, you can use concurrency and let the framework run the two clocks in separate background threads, while all concurrent communication is encapsulated and hidden from the library user. For certain clocks, such as fixed-step clocks, it is also possible to schedule them 'deterministically', which is very useful when e.g. audio and video sampling ratios need to be kept stable.

### [edit] 1.4 Resampling buffers

A resampling buffer is the fundamental asynchronous data component in Rhine. It acceptsThe Rhine library implements standard buffering and resampling techniques such as FIFO queues, linear/cubic/sinc interpolation, first order holds and others. Some buffers, like fixed-rate up-/downsamplers, are annotated with clock types in order to be used only at the correct rate. Others are clock-polymorphic.

### [edit] 1.5 Main loops

Signal functions and resampling buffers are composed to 'signal networks'. In turn, signal networks together with compatible clocks form the main high-level components, calledAll this is done automatically by the framework without inserting any additional side effects. This makes Rhine suitable for reproducible testing, since it hides no IO under the hood.

## [edit] 2 Development

- Rhine is developed on https://github.com/turion/rhine. The development version usually stays up to date with the latest GHC. Support requests for applications developed with Rhine are very welcome there.
- Rhine is on stackage.

### [edit] 2.1 General development guideline

To build an application with Rhine, you can usually follow this guideline:

- Ask yourself what the different synchronous subsystems of the whole application should be. You will recognise them by the rate they work ("tick") at. For example in a game, you might have a user event system, a physical simulation, and a graphics system.
- Ask yourself whether you have any special clocks, e.g. external devices, event machines you need to connect to, external loops you have to connect to. Implement those as clocks in Rhine, ideally using existing components.
- Implement each synchronous subsystem as a clocked signal function.
- Decide how to schedule the different clocks. In most cases, you will be able to use deterministic or concurrent schedules from the library.
- Decide how to resample the data from one subsystem to the other. Take existing resampling buffers from the library, or build your own buffers from existing ones and signal functions.
- Build the main Rhine program from all the components. The clock types will tell you whether you have correctly combined everything.
- Run the main loop and possibly add initialisation and clean up actions.

## [edit] 3 Further resources

- The main article: https://www.manuelbaerenz.de/article/rhine-frp-type-level-clocks
- For an example app, see https://github.com/turion/sonnendemo
- Haskell Symposium 2018 presentation by Iván Pérez: To be uploaded