- Notifications
You must be signed in to change notification settings - Fork37
ivanperez-keera/dunai
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
Dunai is ageneralized reactive programming library on top of which othervariants like Classic FRP, Arrowized FRP and Reactive Values can beimplemented.
Installation •Examples •Documentation •Related projects •Technical information •Contributions •History
Intuitive syntax and semantics.
Composition of effects via use of different monads and transformers.
Isolation of effectful and effect-free reactive functions at type level.
Time-free (time is not explicitly included) and time-able (time can beadded).
Fully extensible.
Can be used to implement other FRP libraries/flavors on top.
Supports applicative, functional and arrowized style.
Programs can be tested with QuickCheck and debugged using Haskell Titan.
To use Dunai, you must have a Haskell compiler installed (GHC). We currentlysupportGHC
versions 7.6.3 to 9.8.1. It likely works with other versions aswell.
On Debian/Ubuntu, both can be installed with:
$ apt-get install ghc cabal-install
On Mac, they can be installed with:
$ brew install ghc cabal-install
Once you have a working set of Haskell tools installed, install Dunai byexecuting:
$ cabal update$ cabal install --lib dunai
Running the following will print the wordSuccess
if installation has gonewell, or show an error message otherwise:
$ runhaskell <<< 'import Data.MonadicStreamFunction; main = putStrLn "Success"'
Open a GHCi session and import the main Dunai module:
$ ghcighci> importData.MonadicStreamFunction
An MSF is a time-varying transformation applied to a series of inputs as theycome along, one by one.
Use the primitivearr :: (a -> b) -> MSF m a b
to turn any pure function intoan MSF that applies the given function to every input. The functionembed :: MSF m a b -> [a] -> m [b]
runs an MSF with a series of inputs, collecting theoutputs:
ghci> embed (arr (+1)) [1,2,3,4,5][2,3,4,5,6]
MSFs can have side effects; hence them
that accompanies the typeMSF
inthe signatures ofarr
andembed
. The functionarrM
turns amonadicfunction of typea -> m b
into an MSF that will constantly apply thefunction to each input.
For example, the functionprint
takes a value and prints it to the terminal(a side effect in theIO
monad), producing an empty()
output. Elevating orliftingprint
into anMSF
will turn it into a processor that prints eachinput passed to it:
ghci>:typeprintprint::Showa=>a->IO()ghci>:type arrMprintarrMprint::Showa=>MSFIOa()
If we now run that MSF with five inputs, all are printed to the terminal:
ghci> embed (arrMprint) [1,2,3,4,5]12345[(),(),(),(),()]
As we can see, after all side effects,embed
collects all the outputs, whichGHCi shows at the end.
When we only care about the side effects and not the output list, we candiscard it withControl.Monad.void
. (Dunai provides an auxiliary functionembed_
for the same purpose.)
ghci> importControl.Monad (void)ghci> void$ embed (arrMprint) [1,2,3,4,5]12345
MSFs can be piped into one another with the functions(>>>)
or(.)
, so thatthe output of one MSF is fed as input to another MSFat each point:
ghci> void$ embed (arr (+1)>>> arrMprint) [1,2,3,4,5]23456
A monadic computation without arguments can be lifted into an MSF with thefunctionconstM
:
ghci>:typegetLinegetLine::IOStringghci>:type constMgetLineconstMgetLine::MSFIOaString
This MSF will get a line of text from the terminal every time it is called,which we can pipe into an MSF that will print it back.
ghci> void$ embed (constMgetLine>>> arrMputStrLn) [(),()]What the user types, the computer repeats.What the user types, the computer repeats.Once again, the computer repeats.Once again, the computer repeats.
Notice how we did not care about the values in the input list toembed
: theonly thing that matters is how many elements it has, which determines how manytimesembed
will run the MSF.
Simulations can run indefinitely with the functionreactimate :: MSF m () () -> m ()
, which is useful when the input to the MSFs being executed is beingproduced by another MSFs, like in the case above withconstM getLine
producing inputs consumed byarrM putStrLn
:
ghci> reactimate (constMgetLine>>> arrreverse>>> arrMputStrLn)HelloolleHHaskell is awesomeemosewa si lleksaH^C
Dunai has a very extensive API and supports many programming styles. MSFs areapplicatives, so we can transform them using applicative style, and they arecategories, so they can be piped into one another withControl.Category.(.)
.For example, the line above can also be written as:
ghci> reactimate (arrMputStrLn. (reverse<$> constMgetLine))
which is equivalent to:
ghci> reactimate (arrMputStrLn.fmapreverse. constMgetLine)
Other writing styles (e.g., arrow notation) are also supported. Thisversatility makes it possible for you to use the notation you feel mostcomfortable with.
MSFs are immensely expressive. With MSFs, you can implement stream programming,functional reactive programming (both classic and arrowized), reactiveprogramming, and reactive values, among many others. The real power of MSFscomes from the ability to carry out temporal transformations (e.g., delays), toapply different transformations at different points in time, and to work withdifferent monads. See the documentation below to understand how capable theyare.
The best introduction to the fundamentals of Monadic Stream Functions is:
The following papers are also related to MSFs:
Fault Tolerant Functional Reactive Programming (extended version)
Testing and Debugging Functional Reactive Programming (mirror)
Actors Design Patterns and Arrowised FRP. Talk by Diego Alonso Blas, describing Monadic Stream Functions and an encoding in scala.
Functional Reactive Programming, Refactored. Original talk describing MSFs. Haskell Symposium 2016.
Back to the Future: Time Travel in FRP. Talk describing how to do time transformations in FRP and MSFs. Haskell Symposium 2017.
Fault Tolerant Functional Reactive Programming. Talk describing how MSFs can be used to add fault tolerance information. ICFP 2018.
Rhine: FRP with Type-level Clocks. Talk describing how MSFs can be extended with clocks. Haskell Symposium 2018.
The Bearriver Arcade. Fun arcade games made using Bearriver.
Haskanoid. Haskell breakout game implemented using the Functional Reactive Programming library Yampa (compatible with Dunai/Bearriver).
ivanperez-keera/Yampa: a fullFRP implementation that has been used extensively in academia, open sourceand industry.
turion/rhine: extension of Dunai withtype-level clocks and explicit coordination.
keera-studios/haskell-titan:an advanced, interactive testing framework with support for step-by-stepexecution and record-and-replay. Haskell-titan supports connecting to dunaisystems via its Yampa-compatible interface library bearriver, via a flag inthe libraries
titan-debug-yampa
andtitan-record-yampa
.
Simpler games will be playable without further optimisations. For example, thegamehaskanoid works well withDunai/Bearriver. You can try it with:
$ git clone https://github.com/ivanperez-keera/haskanoid.git$cd haskanoid/$ cabal install -f-wiimote -f-kinect -fbearriver
It uses unaccelerated SDL 1.2, the speed is comparable to Yampa's:
$ haskanoidPerformance report :: Time per frame: 13.88ms, FPS: 72.04610951008645, Total running time: 1447Performance report :: Time per frame: 16.46ms, FPS: 60.75334143377886, Total running time: 3093Performance report :: Time per frame: 17.48ms, FPS: 57.20823798627002, Total running time: 4841Performance report :: Time per frame: 19.56ms, FPS: 51.12474437627812, Total running time: 6797Performance report :: Time per frame: 19.96ms, FPS: 50.100200400801604, Total running time: 8793Performance report :: Time per frame: 19.44ms, FPS: 51.440329218106996, Total running time: 10737
It runs almost in constant memory, with about 50% more memory consumption thanwithYampa
: 200k for Yampa and 300K for Dunai/Bearriver. (There is very minorleaking, probably we can fix that with seq.)
We have obtained different figures tracking different modules. Inthepaper, we provided figures for the wholegame, but we need to run newer reliable benchmarks including every module andonly definitions fromFRP.Yampa
,FRP.BearRiver
andData.MonadicStreamFunction
.
Dunai includes some benchmarks as part of the main library. You are encouragedto use them to evaluate your pull requests, and to improve the benchmarksthemselves.
If this library helps you, you may want to considerbuying the maintainer acup of coffee.
Discussions
If you have any comments, questions, ideas, or other topics that you think willbe of interest to the Dunai community, start a new discussionhere. Examples include:
- You've created a new game or application that uses Dunai or BearRiver.
- You've written or found a library that helps use Dunai/BearRiver in aparticular domain, or apply it to a specific platform.
- You've written or found a paper that mentions Dunai/BearRiver.
- You have an idea for an extension that will enable writing programs that arenot currently possible or convenient to capture.
- You think you've found a bug.
- You want to propose an improvement (e.g., make the code faster or smaller).
- You have a question.
- Something in the documentation, a tutorial or a Dunai / BearRiver / FRP paperis unclear.
- You like the project and want to introduce yourself.
Issues
If a specific change is being proposed (either a new feature or a bug fix), youcanopen an issue documenting the proposed changehere.
If you are unsure about whether your submission should be filed as an issue oras a discussion, file it as a discussion. We can always move it later.
Pull requests
Once we determine that an issue will be addressed, we'll decide who does it andwhen the change will be added to Dunai. Even if you implement the solution,someone will walk you through the steps to ensure that your submission conformswith our version control process, style guide, etc. More information on ourprocess is included below.
Please, do not just send a PR unless there is an issue for it and someone fromthe Dunai team has confirmed that you should address it. The PR isverylikely to be rejected, and we really want to accept your contributions, so itwill make us very sad. Open a discussion / issue first and let us guide youthrough the process.
This project is split in three parts:
- Dunai: a reactive library that combines monads and arrows.
- BearRiver: Yampa implemented on top of Dunai.
- Examples: ballbounce
- sample applications that work both on traditional Yampa and BearRiver.
Dunai also includes some benchmarks as part of the main library. You areencouraged to use them to evaluate your pull requests, and to improve thebenchmarks themselves.
We followthis style guide.
We followgit flow.In addition:
- Please document your commits clearly and separately.
- Always refer to the issue you are fixing in the commit summary line with thetext
Refs #<issue_number>
at the end. - If there is no issue for your change, then open an issue first and documentwhat you are trying to achieve/improve/fix.
- Do not address more than one issue per commit or per PR. If two changes arenot directly related to one another, they belong in different PRs, issues andcommits.
- Document what you did in the respective CHANGELOGs in a separate commitbefore you send a PR. This commit should be the last one in the PR.
- If your commit pertains to one package only, name the package at thebeginning of the summary line with the syntax
<package_name>: <...rest_of_summary...>
. - Make sure your changes conform to thecodingstyle.
See the recent repo history for examples of this process. Using a visual repoinspection tool likegitk
may help.
The versioning model we use is the standard in Haskell packages. Versions havethe format<PUB>.<MAJOR>.<MINOR>(.<PATCH>)?
where:
<PUB>
is just a way to signal important milestones or used for promotionalreasons (to indicate a major advancement). A zero on this position has nospecial meaning.<MAJOR>
increases on incompatible API changes.<MINOR>
increases on backwards-compatible changes.<PATCH>
(optional) increases on small changes that do not affect behavior(e.g., documentation).
This library Dunai was created by Ivan Perez and Manuel Baerenz. It is namedafter the Dunai (aka. Danube, or Дунай) river, one of the main rivers inEurope, originating in Germany and touching Austria, Slovakia, Hungary,Croatia, Serbia, Romania, Bulgaria, Moldova and Ukraine.
Other FRP libraries, like Yampa and Rhine, are named after rivers. Dunai hasbeen chosen due to the authors' relation with some of the countries it passesthrough, and knowing that this library has helped unite otherwise verydifferent people from different backgrounds.
About
Classic FRP, Arrowized FRP, Reactive Programming, and Stream Programming, all via Monadic Stream Functions