Code Monkey home page Code Monkey logo

rhine's Introduction

README


Build Status Version on Hackage

Rhine is a library for synchronous and asynchronous Functional Reactive Programming (FRP). It separates the aspects of clocking, scheduling and resampling from each other, and ensures clock-safety on the type level.

Versions 1.* vs. 0.*

Confused because some examples from the article don't work anymore? As a big simplification and breaking change, explicit schedules were removed in version 1.0. For an overview of the required changes, see this page.

Concept

Complex reactive programs often process data at different rates. For example, games, GUIs and media applications may output audio and video signals, or receive user input at unpredictable times. Coordinating these different rates is a hard problem in general. If not enough care is taken, buffer underruns and overflows, space and time leaks, accidental synchronisation of independent sub-systems, and concurrency issues, such as deadlocks, may all occur.

Rhine tackles these problems by annotating the signal processing components with clocks, which hold the information when data will be input, processed and output. Different components of the signal network will become active at different times, or work at different rates. If components running under different clocks need to communicate, it has to be decided when each component becomes active ("scheduling"), and how data is transferred between the different rates ("resampling"). Rhine separates all these aspects from each other, and from the individual signal processing of each subsystem. It offers a flexible API to all of them and implements several reusable standard solutions. In the places where these aspects need to intertwine, typing constraints on clocks come into effect, enforcing clock safety.

Example

A typical example, which can be run as cd rhine-examples/ && cabal run Demonstration, (or using nix flakes with nix develop followed cabal run Demonstration), would be:

  -- | Create a simple message containing the time stamp since initialisation,
  --   for each tick of the clock.
  --   Since 'createMessage' works for arbitrary clocks (and doesn't need further input data),
  --   it is a 'Behaviour'.
  --   @time@ is the 'TimeDomain' of any clock used to sample,
  --   and it needs to be constrained in order for time differences
  --   to have a 'Show' instance.
  createMessage
    :: (Monad m, Show (Diff time))
    => String
    -> Behaviour m time String
  createMessage str
    =   timeInfoOf sinceInit >-> arr show
    >-> arr (("Clock " ++ str ++ " has ticked at: ") ++)

  -- | Output a message /every second/ (= every 1000 milliseconds).
  --   Let us assume we want to assure that 'printEverySecond'
  --   is only called every second,
  --   then we constrain its type signature with the clock @Millisecond 1000@.
  printEverySecond :: Show a => ClSF IO (Millisecond 1000) a ()
  printEverySecond = arrMCl print

  -- | Specialise 'createMessage' to a specific clock.
  ms500 :: ClSF IO (Millisecond 500) () String
  ms500 = createMessage "500 MS"


  ms1200 :: ClSF IO (Millisecond 1200) () String
  ms1200 = createMessage "1200 MS"

  -- | Create messages every 500 ms and every 1200 ms,
  --   collecting all of them in a list,
  --   which is output every second.
  main :: IO ()
  main = flow $
    ms500 @@ waitClock            --  a Rhine = a ClSF in the context of a Clock
    |@|                           --  compose 2 Rhines in parallel
    ms1200 @@ waitClock           --  a Rhine at a different clock
    >-- collect -->               --  buffer results from both Rhines into a list
    printEverySecond @@ waitClock --  the final Rhine

  -- | Uncomment the following for a type error (the clocks don't match):

  -- typeError = ms500 >>> printEverySecond

This repository

  • rhine/: The main library, which is also mirrored on hackage.
  • rhine-gloss/: A wrapper library to gloss, a functional OpenGL library.
  • rhine-bayes/: A library for stochastic processes and online machine learning, using monad-bayes.
  • rhine-terminal/: A wrapper library to terminal, a library to write terminal applications.
  • rhine-examples/: Different examples as a starting point to learn Rhine.

Documentation

The best way to learn about Rhine is currently the article Rhine: FRP with Type-Level Clocks.

For a quick reference of the most important concepts, see the cheatsheet.

Additional documentation

FAQ

  • Why does my blocking code, e.g. arrMCl readLn, behave erratically?

Clocks must be the only things that block a thread, not ClSFs. So for example, you can fix:

arrMCl readLn

by using:

tagS >>> arr read :: ClSF IO StdinClock () Int

tagS contains the string that the StdinClock grabbed from stdin, and only the clock has been allowed to block the thread!

  • Can a sampling schedule dynamically change, e.g. depend on a signal?

Yes, for instance you could implement a distance-dependent collision detector.

  • How to handle slow computations, i.e. computations that take longer than the sample rate?

Several strategies exist and it depends on your use case. For FixedStep clocks, it won't matter since the execution of the program isn't tied to a realtime clock. For ClSFs running on UTCTime clocks, you can execute the slow code in a separate thread and coordinate merging the results back into the signal network.

Development

See Contributing.md for details.

  • Rhine usually follows up-to-date GHC versions.
  • Contributions are welcome! There are always a few issues labelled help needed, in case you're looking for an easy way to get started.
  • Rhine is a beginner-friendly Haskell project! Even if you're new to Haskell and FRP, you can contribute. This is a good place to start contributing to open-source projects. Have a look at issues labelled good first issue. If you have questions, don't hesitate to ask on Github.

Related projects

rhine's People

Contributors

alexpeits avatar dependabot[bot] avatar freckletonj avatar ggreif avatar jmatsushita avatar miguel-negrao avatar ners avatar smunix avatar solviqorda avatar turion avatar walseb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rhine's Issues

Big renames

Renames

renames we would like to have:

  • TimeDomainOf -> Time
  • SyncSF -> ClSF
  • SF -> SN ("signal network")
  • td -> time
  • arrMSync_ :: m a -> SyncSF m cl arbitrary a to constMSync, and then to constMCl

Renames we have to think about once more:

  • Step ->FixedStep?
  • CycleClock -> Periodic?
  • theTag :: SyncSF m cl a (Tag cl) -> tagS?
  • sinceStart -> sinceInit? And:
    • startClock -> initClock
    • startSchedule -> initSchedule
  • sinceSimStart -> ? (#30 )

Won't rename

  • KeepLast -> ZeroOrderHold
  • ResamplingBuffer.put -> ResamplingBuffer.input
  • ResamplingBuffer.get -> ResamplingBuffer.output

Aliases to add

  • ResBuf
  • ParClock
  • SeqClock
  • absoluteS = timeInfoOf absolute, sinceStartS = timeInfoOf sinceStart, sinceTickS = timeInfoOf sinceTick

ToDo

Consistency checks

  • Haddock errors
  • Skim through code in PR in order to find alignment mistakes

Changes in other places (immediate)

  • Article

Changes in other places (whenever they update to rhine-0.5)

  • sonnendemo
  • tutorial

Implement schedule for Cycle clocks and base Step clock on Cycle

The FRP.Rhine.Cycle clocks give an implementation of pure, cyclically repeating clocks:
CycleClock '[500, 1000] would tick at 500, 1500, 2000, 3000, 3500 and so on. (See the CycleClock example in rhine-examples.)

In principle, the clock Step n is nothing else than CycleClock '[n], so this should be a type synonym.

But there exists a schedule for Step n clocks which doesn't exist yet for CycleClocks, so it should be generalised to CycleClock and then Step n refactored as a synonym.

(Also note #45 )

Failure to compile with GHC < 8.2

I'm getting the following:

src/FRP/Rhine/Clock/Select.hs:30:37:
    Wildcard not allowed
    In type family instance equation of ‘TimeDomainOf’:
      TimeDomainOf (SelectClock cl _)
cabal: Leaving directory '/tmp/cabal-tmp-14310/rhine-0.1.1.0'
Updating documentation index
/home/dash/tmp/rhine-tutorial/.cabal-sandbox/share/doc/x86_64-linux-ghc-7.10.3/index.html
cabal: Error: some packages failed to install:
rhine-0.1.1.0 failed during the building phase. The exception was:
ExitFailure 1

Maybe this need for GHC 8.2 can be specified in the cabal file somehow.

Change GHC extensions formatting convention

To do

Change from

{-# LANGUAGE SomeExtension      #-}
{-# LANGUAGE SomeOtherExtension #-}

to

{-# LANGUAGE SomeExtension #-}
{-# LANGUAGE SomeOtherExtension #-}

Reason

  • It's not much uglier
  • Only O(1) change when adding or removing an extension
  • Much git-friendlier

Synchronise stack resolvers

  • Choose the same stack resolvers on all projects (ideally the newest that works)
  • If necessary, update dependency versions

Generalise concurrent schedule

Use the MonadConc class from concurrency to generalise the type signature of the concurrently schedule. Find out whether it overlaps with the Reader and Writer schedules.

FRP.Rhine should export all clocks, schedules and resampling buffers

Currently, FRP.Rhine exports everything about SyncSFs, the definition of the Clock type class and the Schedule and ResamplingBuffer types. But it would be better and without disadvantage to export all clocks, schedules and buffers as well. Ideally, when creating a rhine project, you just need to import FRP.Rhine, and that's it.

Implement sprites

There should be a minimal implementation of sprites, which are basically pictures, with the following features:

  • They should contain a picture and a position
  • They should be clickable (i.e. there is an event clock (subclock of the main event clock) that ticks on sprite click events)

Implement event clock

Implement a clock in a special monad m (or monad transformer) that ticks upon a particular side effect in m (depending on the clock value).
Other parts of the signal network that are also in m can then trigger events on this clock.

One crude implementation could go roughly like:

data MVarEventClock a = MVarEventClock (MVar a)

instance Clock IO MVarEventClock where
  startClock (MVarEventClock var) = arrM_ getCurrentTime &&& arrM_ (takeMVar var)

triggerEvent :: MVarEventClock a -> a -> IO ()
triggerEvent (MVarEventClock var) = putMVar var

Care needs to be taken that triggerEvent does not lead to deadlocks when called from a signal on the same clock. Possibly, Chan or STM.TChan will give a more robust implementation.

Is it possible to implement such a clock purely?

Monoid collect buffer

There should be a ResamplingBuffer that collects the incoming data, which has to be a monoid, via mappend.

Merge rhine-tutorial into this repo

https://github.com/turion/rhine-tutorial should be part of this repo.

Advantages

  • Track all issues here
  • Have documentation closer together
  • Travis integration of everything in one place
  • Coherent version updates for main library and all subprojects

ToDo

  • Decide whether we need the version history of the other repo (No.)
  • Decide what to do with the initial and final status (Separate files, both compilable and executable.)
  • Merge the files in here
  • Merge issues here and convert any TODOs and problems to issues
  • Add the tutorial to travis
  • Close the other repo and add info to the Readme there

HoistClock: Type indexes must match class instance head

@ivanperez-keera, this is another issue you raised:

src/FRP/Rhine/Clock.hs:135:8: error:
    • Type indexes must match class instance head
      Expected: TimeDomainOf (HoistClock m1 m2 a)
        Actual: TimeDomainOf (HoistClock m1 m2 cl)
    • In the type instance declaration for ‘TimeDomainOf’
      In the instance declaration for ‘Clock m2 (HoistClock m1 m2 a)’
    |
135 |   type TimeDomainOf (HoistClock m1 m2 cl) = TimeDomainOf cl
    |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

src/FRP/Rhine/Clock.hs:136:8: error:
    • Type indexes must match class instance head
      Expected: Tag (HoistClock m1 m2 a)
        Actual: Tag (HoistClock m1 m2 cl)
    • In the type instance declaration for ‘Tag’
      In the instance declaration for ‘Clock m2 (HoistClock m1 m2 a)’
    |
136 |   type Tag          (HoistClock m1 m2 cl) = Tag          cl
    |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 

Understand the relation between ResamplingBuffers, lenses and reactive values

(Time-unaware) resampling buffers are roughly:

data ResBuf m a b = ResBuf
  { get :: m (b, ResBuf m a b)
  , put :: a -> m (ResBuf m a b)
  }

This is a final coalgebra, a general coalgebra is something like:

data ResBuf m a b s = ResBuf
  { get :: StateT s m b
  , put :: a -> StateT s m ()
  }

This must be related to lenses somehow, although it is unclear to me right now how precisely. Hints and ideas welcome. This is not an urgent issue, but an open question.

Keera hails reactive values are somehow related, except they work without state/continuations but just IO.

Space leak in ResamplingBuffers

It's possible to build an unintended space leak in a resampling buffer if data is produced very quickly and accumulates on the buffer. So far that's not surprising, for some buffers (i.e. collect, unbounded queues), that's what has to be expected.

I'm annoyed that it's also possible to create space leaks unintentionally. There is an example in https://github.com/turion/rhine/tree/dev_spaceleak. On one clock, a lot of numbers are produced, and sent to the buffer in order to be added there. On the other clock, the current sum is retrieved. In principle, it should be possible to just add the numbers up strictly, but it's not obvious to me how to achieve that.

Remove/refactor Count and FixedRate

  • FixedRate is against the Rhine philosophy that sample rates should be type-level. There are better alternatives now (Step/FixedStep), and it should be removed.
  • Count is just Step 1/FixedStep 1. Should be a type synonym, and moved into the FixedStep module.

Write audio backend examples for ADSR

Currently, the ADSR example is a behaviour, which means that it can run on any clock. It would be nice to have an audio clock on which it runs, and a user interface (console is ok) so one can actually hear what's going on.

Bounded FIFO & LIFO buffers

For the fifo and lifo buffers, implement a bounded version that forgets the oldest values when the size is above a given threshold.

Implement rhine-gloss bindings with a more relaxed clock setting

Right now, rhine-gloss apps have to have a rigid clock topology. The advantage is a completely side-effect free framework, but the disadvantage is that they have to be basically synchronous.

It should be easy to use playIO from gloss and wormhole (or event) clocks in IO that communicate with gloss. They can then be incorporated in an app with a completely free clock topology.

Have forkOS versions of concurrent schedules?

It might be a good idea to have forkOS versions of the concurrent schedules.
This probably has benefits e.g. for OpenGL, but needs testing.

Another possibility would be to just use forkOS instead of forkIO directly. The overhead shouldn't be so high since only a handful are created at the beginning of the program, and never during the flow.

Supply interfaces for lifting other transformers in SyncSFs

Dunai's exception handling has been ported to Rhine's SyncSFs with some boiler plate code. This is still missing for most other transformers (e.g. #13 is a subissue of this).

The main issue is always that one can't directly apply the functions from Dunai's Control.Monad.Trans.MSF.*, since in a SyncSF, the outermost monad layer is ReaderT.
So one needs to commute some transformer past the outermost ReaderT layer of SyncSF,
then handle it, and possibly commute it back.
Doing this in a systematic way (e.g. a type class) would maybe clean up code and simplify progress.

Timers should output overdue time

In FRP.Rhine.ClSF.Util, timers throw () as an exception when the time is up. But for undersampling, this is sometimes not ideal, as the exception might get thrown long after the time is up. So the timer should raise the overdue time as an exception, which can then be handled further.

Complete SyncSF.Except

Quite a few functions that exist in the corresponding dunai module (to be found here) have not been translated yet to SyncSFs. It's mostly about pushing the top ReaderT layer around to access the ExceptT layer beneath, and plenty of working examples exist.

Deepseq wormholes

  • Wormholes have to be cleaned up (just code on my machine) and tested
  • A completely strict deepseq wormhole has to be written (so you can do heavy calculations in a separate thread). This is actually a dunai issue, but I'm not touching dunai's wormholes because of ivanperez-keera/dunai#100

Schedule SelectClock with main clock

In FRP.Rhine.Clock.Select, there is a schedule for two SelectClocks of the same main clock, schedSelectClocks. There should be a schedule for a SelectClock and its main clock.

Resolve fifo name clash

Currently, Data.MonadicStreamFunction.Util from Dunai exports fifo, but FRP.Rhine.ResamplingBuffer.FIFO does as well.

3 options:

  • Don't reexport Dunai's fifo. Maybe leave a note in the docs?
  • Rename Dunai's fifo. Probably unmotivated from upstream perspective.
  • Rename Rhine's fifo, maybe to fifoUnbounded. Whatever decision, propagate it to lifo.

Add rhine and subpackages to stackage

Whenever I publish a package
on the packages server, called hackage,
then, for rhine to shine brightly,
I'll commit unto nightly,
the package on hackage to stackage.

Publish info about Rhine

There are some places where info about Rhine should be posted in a few months:

  • The Haskell wiki
    • The FRP page
    • Other pages
  • Mailinglists (which? Yampa, ...)
  • Add the article to this repository

If you know about other places where such info should appear, comment here.

Hoist clock trees and rhines along monad morphisms

Generalise #62 in such a way that arbitrary clock trees can be hoisted. Two problems occur:

  • This changes the type of the clock, which suggests a type family type HoistedClock SequentialClock cl1 cl2 = SequentialClock (HoistedClock cl1) (HoistedClock cl2).
  • It's hard to define functions on type families, i.e. see https://github.com/turion/rhine/pull/61/files#r190572641 . It seems there can't be a function hoistedClock :: (forall a . m a -> m' a) -> cl -> HoistedClock m m' cl with a type family as described above.

Possibly the types SequentialClock and ParallelClock and the whole type family business should be given up in favour of a data kind representing clock trees. (I have some notes in that direction which don't compile fully yet.)

Implement transformer handling for Rhines

Right now, it is possible to escape transformers in SyncSFs, e.g. given syncSF :: SyncSF (ExceptT e m) a b, we can use FRP.Rhine.SyncSF.Except to handle the exceptions and remove the ExceptT layer. Solving #14, this can be done for other transformers as well.

But there is no such utility to do so for Rhines, although it is conceivable to do this for several monad transformers such as ReaderT. For example, we'd like a type signature like this:

runReaderR :: Rhine (ReaderT r m) cl a b -> r -> Rhine m cl a b

It's a bit unclear what happens to cl, i.e. whether its type needs to be transformed as well. This depends on whether cl is polymorphic in m or whether it depends on a particular type. In the latter case, we'd have to have a HoistedClock or similar.

It's harder to say how exception handling would work on whole Rhines, since one would have to decide whether to restart the clock upon handling the exception, with the new clock.

Implement Semigroup-Monoid-Proposal (needed for GHC 8.4.1)

    [24 of 29] Compiling FRP.Rhine.Clock.Realtime.Stdin ( src/FRP/Rhine/Clock/Realtime/Stdin.hs, .stack-work/dist/x86_64-linux/Cabal-2.2.0.1/build/FRP/Rhine/Clock/Realtime/Stdin.o )
    
    /home/ggreif/stack16020/rhine-0.4.0.0/src/FRP/Rhine/Clock/Realtime/Stdin.hs:33:10: error:
        ? No instance for (Semigroup StdinClock)
            arising from the superclasses of an instance declaration
        ? In the instance declaration for ?Monoid StdinClock?
       |
    33 | instance Monoid StdinClock where
       |          ^^^^^^^^^^^^^^^^^

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.