Dunai
Classic FRP, Arrowized FRP, Reactive Programming, and Stream Programming, all via Monadic Stream Functions
Install / Use
/learn @ivanperez-keera/DunaiREADME
Dunai
Dunai is a generalized reactive programming library on top of which other variants like Classic FRP, Arrowized FRP and Reactive Values can be implemented.
Installation • Examples • Documentation • Related projects • Technical information • Contributions • History
</div>Features
-
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 be added).
-
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.
Table of Contents
Installation
<sup>(Back to top)</sup>
Pre-requisites
<sup>(Back to top)</sup>
To use Dunai, you must have a Haskell compiler installed (GHC). We currently
support GHC versions 7.6.3 to 9.8.1. It likely works with other versions as
well.
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
Compilation
<sup>(Back to top)</sup>
Once you have a working set of Haskell tools installed, install Dunai by executing:
$ cabal update
$ cabal install --lib dunai
Running the following will print the word Success if installation has gone
well, or show an error message otherwise:
$ runhaskell <<< 'import Data.MonadicStreamFunction; main = putStrLn "Success"'
Examples
<sup>(Back to top)</sup>
Open a GHCi session and import the main Dunai module:
$ ghci
ghci> import Data.MonadicStreamFunction
An MSF is a time-varying transformation applied to a series of inputs as they come along, one by one.
Use the primitive arr :: (a -> b) -> MSF m a b to turn any pure function into
an MSF that applies the given function to every input. The function embed :: MSF m a b -> [a] -> m [b] runs an MSF with a series of inputs, collecting the
outputs:
ghci> embed (arr (+1)) [1,2,3,4,5]
[2,3,4,5,6]
MSFs can have side effects; hence the m that accompanies the type MSF in
the signatures of arr and embed. The function arrM turns a monadic
function of type a -> m b into an MSF that will constantly apply the
function to each input.
For example, the function print takes a value and prints it to the terminal
(a side effect in the IO monad), producing an empty () output. Elevating or
lifting print into an MSF will turn it into a processor that prints each
input passed to it:
ghci> :type print
print :: Show a => a -> IO ()
ghci> :type arrM print
arrM print :: Show a => MSF IO a ()
If we now run that MSF with five inputs, all are printed to the terminal:
ghci> embed (arrM print) [1,2,3,4,5]
1
2
3
4
5
[(), (), (), (), ()]
As we can see, after all side effects, embed collects all the outputs, which
GHCi shows at the end.
When we only care about the side effects and not the output list, we can
discard it with Control.Monad.void. (Dunai provides an auxiliary function
embed_ for the same purpose.)
ghci> import Control.Monad (void)
ghci> void $ embed (arrM print) [1,2,3,4,5]
1
2
3
4
5
MSFs can be piped into one another with the functions (>>>) or (.), so that
the output of one MSF is fed as input to another MSF at each point:
ghci> void $ embed (arr (+1) >>> arrM print) [1,2,3,4,5]
2
3
4
5
6
A monadic computation without arguments can be lifted into an MSF with the
function constM:
ghci> :type getLine
getLine :: IO String
ghci> :type constM getLine
constM getLine :: MSF IO a String
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 (constM getLine >>> arrM putStrLn) [(), ()]
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 to embed: the
only thing that matters is how many elements it has, which determines how many
times embed will run the MSF.
Simulations can run indefinitely with the function reactimate :: MSF m () () -> m (), which is useful when the input to the MSFs being executed is being
produced by another MSFs, like in the case above with constM getLine
producing inputs consumed by arrM putStrLn:
ghci> reactimate (constM getLine >>> arr reverse >>> arrM putStrLn)
Hello
olleH
Haskell is awesome
emosewa si lleksaH
^C
Dunai has a very extensive API and supports many programming styles. MSFs are
applicatives, so we can transform them using applicative style, and they are
categories, so they can be piped into one another with Control.Category.(.).
For example, the line above can also be written as:
ghci> reactimate (arrM putStrLn . (reverse <$> constM getLine))
which is equivalent to:
ghci> reactimate (arrM putStrLn . fmap reverse . constM getLine)
Other writing styles (e.g., arrow notation) are also supported. This versatility makes it possible for you to use the notation you feel most comfortable with.
MSFs are immensely expressive. With MSFs, you can implement stream programming, functional reactive programming (both classic and arrowized), reactive programming, and reactive values, among many others. The real power of MSFs comes from the ability to carry out temporal transformations (e.g., delays), to apply different transformations at different points in time, and to work with different monads. See the documentation below to understand how capable they are.
Documentation
<sup>(Back to top)</sup>
Publications
<sup>(Back to top)</sup>
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)
Videos
<sup>(Back to top)</sup>
-
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.
Related projects
<sup>(Back to top)</sup>
Games
<sup>(Back to top)</sup>
-
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).
Libraries
- ivanperez-keera/Yampa: a full FRP implementation that has been used extensively in academia, open source and industry
