Dunai

repository·develop·Indexed 42 days ago

https://github.com/ivanperez-keera/dunai

A generalized reactive programming library based on Monadic Stream Functions (MSFs). It provides an extensible framework for implementing various Functional Reactive Programming (FRP) paradigms, including Classic FRP and Arrowized FRP, by allowing the composition of effects via monads and transformers. It supports GHC versions 7.6.3 to 9.8.1.

Tokens
1.6K
Snippets
8
Records
12
Agent score
4%

What's inside Dunai

  1. What is a Monadic Stream Function (MSF)?

    develop

    A Monadic Stream Function (MSF) is a time-varying transformation applied to a series of inputs as they arrive one by one. In Dunai, MSFs are the core abstraction and can be used to implement various flavors of Functional Reactive Programming (FRP), such as Classic FRP or Arrowized FRP.

    Key characteristics:

    • Compositional: MSFs can be composed using monadic, applicative, or arrowized styles.
    • Effectful: They can carry side effects via a monad m (e.g., MSF IO a b).
    • Versatile: They support temporal transformations (like delays) and can be used for stream programming or reactive values.
  2. Understand the Dunai project structure

    develop

    The project is organized into three distinct parts:

    • Dunai: A reactive library that combines monads and arrows.
    • BearRiver: An implementation of Yampa built on top of Dunai.
    • Examples: Sample applications (such as ballbounce) designed to work with both traditional Yampa and BearRiver.
  3. Compare MSFs with 'varying' and 'Auto'

    develop

    When choosing between Dunai's MSFs and other FRP implementations, consider the following comparisons:

    MSFs vs varying

    In principle, the concepts of MSFs and varying are isomorphic and equally expressive.

    MSFs vs Auto

    Auto provides a larger set of constructors and includes built-in support for serialization and de-serialization, which MSFs do not natively prioritize.

  4. Understand the relationship between MSFs and FRP

    develop

    Dunai's core abstraction, Monadic Stream Functions (MSFs), can be used to implement various forms of Functional Reactive Programming (FRP), including:

    • Classic FRP
    • Arrowized FRP
    • Wormholes (as described by Daniel Winograd-Cort)

    This directory (dunai-examples/reversefrpzoo) serves as a collection of examples demonstrating how MSFs can be applied to implement these different FRP paradigms.

  5. Install Dunai

    develop

    To install Dunai, ensure you have a Haskell compiler (GHC) installed. Supported GHC versions are 7.6.3 to 9.8.1.

    Pre-requisites

    Debian/Ubuntu:

    apt-get install ghc cabal-install

    macOS:

    brew install ghc cabal-install

    Compilation

    Once the Haskell tools are installed, use cabal to install the library:

    cabal update
    cabal install --lib dunai

    To verify the installation, run the following command. It should print Success:

    runhaskell <<< 'import Data.MonadicStreamFunction; main = putStrLn "Success"'
  6. Evaluate performance with haskanoid

    develop

    For a practical performance reference, you can test the haskanoid game, which is compatible with Dunai/BearRiver. It uses unaccelerated SDL 1.2 and exhibits performance comparable to Yampa, running with almost constant memory (approximately 300K for Dunai/Bearriver vs 200K for Yampa).

    $ git clone https://github.com/ivanperez-keera/haskanoid.git
    $ cd haskanoid/
    $ cabal install -f-wiimote -f-kinect -fbearriver
  7. Compose MSFs using `(>>>)` or `(.)`

    develop

    MSFs can be piped into one another so that the output of one MSF is fed as the input to the next at every time step. You can use the category composition operator (>>>) or the standard function composition operator (.).

    -- Using (>>>)
    void $ embed (arr (+1) >>> arrM print) [1,2,3,4,5]
    
    -- Using (.)
    void $ embed (arr (+1) >>> arrM print) [1,2,3,4,5]
  8. Run MSFs with a series of inputs using `embed`

    develop

    The embed function runs an MSF with a provided list of inputs and collects the resulting outputs into a list. The length of the input list determines how many steps the MSF runs.

    -- Runs the MSF and returns the list of outputs
    embed (arr (+1)) [1,2,3,4,5]
    -- Result: [2,3,4,5,6]
    
    -- If you only care about side effects and want to discard the output list,
    -- use 'embed_' or 'void $ embed ...'
    import Control.Monad (void)
    void $ embed (arrM print) [1,2,3,4,5]
  9. Run indefinite simulations with `reactimate`

    develop

    For simulations that should run indefinitely (where inputs are produced by other MSFs or external sources rather than a finite list), use reactimate. It takes an MSF of type MSF m () () and runs it within the monad m.

    -- Example: An indefinite loop that reads from stdin, reverses the string, and prints it
    reactimate (constM getLine >>> arr reverse >>> arrM putStrLn)
  10. Lift monadic computations into MSFs with `constM`

    develop

    The constM function lifts a monadic computation that takes no arguments (type m a) into an MSF (MSF m b a). This MSF will execute the monadic action every time it is called, effectively producing a stream of values from a side-effecting source.

    -- Example: Creating a stream of user inputs from the terminal
    -- 'getLine' is 'IO String'
    -- 'constM getLine' is 'MSF IO a String'
    
    void $ embed (constM getLine >>> arrM putStrLn) [(), ()]
  11. Convert pure functions to MSFs with `arr`

    develop

    The arr function turns any pure function of type (a -> b) into an MSF MSF m a b that applies the given function to every input in the stream.

    -- Example: Incrementing every input in a stream
    embed (arr (+1)) [1,2,3,4,5]
    -- Output: [2,3,4,5,6]
  12. Convert monadic functions to MSFs with `arrM`

    develop

    The arrM function turns a monadic function of type a -> m b into an MSF that constantly applies the function to each input. This allows MSFs to perform side effects (like printing to a terminal) at each step of the stream.

    -- Example: Printing each input to the terminal
    -- If 'print' is 'Show a => a -> IO ()'
    -- 'arrM print' becomes 'MSF IO a ()'
    
    embed (arrM print) [1,2,3,4,5]
    -- Output:
    -- 1
    -- 2
    -- 3
    -- 4
    -- 5
    -- [(), (), (), (), ()]