All Projects → jeremykross → recurrent

jeremykross / recurrent

Licence: other
A library for building functional-reactive (FRP) GUIs in Clojurescript

Programming Languages

clojure
4091 projects
Makefile
30231 projects

Projects that are alternatives of or similar to recurrent

purescript-pop
😃 A functional reactive programming (FRP) demo created with PureScript events and behaviors.
Stars: ✭ 33 (-32.65%)
Mutual labels:  web-development, functional-reactive-programming, frp
Keera Hails
Keera Hails: Haskell on Rails - Reactive Programming Framework for Interactive Haskell applications
Stars: ✭ 153 (+212.24%)
Mutual labels:  functional-reactive-programming, frp
Reflex Vty
Build terminal applications using functional reactive programming (FRP) with Reflex FRP.
Stars: ✭ 117 (+138.78%)
Mutual labels:  functional-reactive-programming, frp
reflex-examples
See Reflex FRP in action with tinker-friendly code samples you can run yourself.
Stars: ✭ 76 (+55.1%)
Mutual labels:  functional-reactive-programming, frp
reflex-dom-semui
A reflex-dom API for Semantic UI components
Stars: ✭ 22 (-55.1%)
Mutual labels:  functional-reactive-programming, frp
Sodium Typescript
Typescript/Javascript implementation of Sodium FRP (Functional Reactive Programming) library
Stars: ✭ 102 (+108.16%)
Mutual labels:  functional-reactive-programming, frp
Kickstarter Reactiveextensions
A collection of extensions to the ReactiveSwift framework.
Stars: ✭ 183 (+273.47%)
Mutual labels:  functional-reactive-programming, frp
Reflex
Interactive programs without callbacks or side-effects. Functional Reactive Programming (FRP) uses composable events and time-varying values to describe interactive systems as pure functions. Just like other pure functional code, functional reactive code is easier to get right on the first try, maintain, and reuse.
Stars: ✭ 910 (+1757.14%)
Mutual labels:  functional-reactive-programming, frp
RxUploader
Uploader for Android using RxJava
Stars: ✭ 72 (+46.94%)
Mutual labels:  functional-reactive-programming, frp
Swift-3-Functional-Programming
Code repository for Swift 3 Functional Programming, published by Packt
Stars: ✭ 78 (+59.18%)
Mutual labels:  functional-reactive-programming, frp
reflex-native
Framework for writing fully native apps using Reflex, a Functional Reactive Programming library for Haskell.
Stars: ✭ 40 (-18.37%)
Mutual labels:  functional-reactive-programming, frp
Rerxswift
ReRxSwift: RxSwift bindings for ReSwift
Stars: ✭ 97 (+97.96%)
Mutual labels:  functional-reactive-programming, frp
Ios Oss
Kickstarter for iOS. Bring new ideas to life, anywhere.
Stars: ✭ 7,840 (+15900%)
Mutual labels:  functional-reactive-programming, frp
Dunai
Classic and Arrowized Functional Reactive Programming, Reactive Programming, and Stream programming, all via Monadic Stream Functions
Stars: ✭ 115 (+134.69%)
Mutual labels:  functional-reactive-programming, frp
Ulmus
A functional-reactive style programming library for Clojure(script)
Stars: ✭ 33 (-32.65%)
Mutual labels:  functional-reactive-programming, frp
Combinerxswiftperformance
A test suite comparing the performance of Combine and RxSwift
Stars: ✭ 154 (+214.29%)
Mutual labels:  functional-reactive-programming, frp
Turbine
Purely functional frontend framework for building web applications
Stars: ✭ 651 (+1228.57%)
Mutual labels:  functional-reactive-programming, frp
Android Oss
Kickstarter for Android. Bring new ideas to life, anywhere.
Stars: ✭ 5,627 (+11383.67%)
Mutual labels:  functional-reactive-programming, frp
yave
Functional visual programming language with FRP for multimedia
Stars: ✭ 29 (-40.82%)
Mutual labels:  functional-reactive-programming, frp
mutable
State containers with dirty checking and more
Stars: ✭ 32 (-34.69%)
Mutual labels:  functional-reactive-programming, frp


Table of Contents

Introduction

Recurrent is a UI library for the web built around the functional-reactive style of programming. It's influenced by the likes of React and other v-dom based libraries. Whereas React has one point of reactivity (state -> UI), Recurrent is deeply reactive throughout.

Usage

[recurrent "0.2.1"]

or

{recurrent {:mvn/version "0.2.1"}}

Components

Components in Recurrent are functions of data. They're built using a special data type, called a signal, which represents a value that changes over time. These are composed using the standard functional tooling (map, filter, reduce, et al.). These functions yield derived signals that will update when their constituent signals change. Many readers will recognize this as the functional reactive style of programming (FRP). The FRP layer in Recurrent is currently handled by Ulmus.

The simplest component looks something like this:

(require [recurrent.core :as recurrent :include-macros true])
(require [ulmus.signal :as ulmus])

(recurrent/defcomponent Hello
  [] ; No arguments
  {:recurrent/dom-$
   (ulmus/signal-of
     [:h1 {} "Hello World"])})

defcomponent is conceptually similar to defn. It takes a vector of arbitary arguments. The return value must be a map with a signal at recurrent/dom-$ and potentially other data. The :recurrent/dom-$ signal represents the component's Hiccup formatted DOM. In this case the signal isn't reactive. It's displays "hello world" in a header.

But becuase :recurrent/dom-$ is a signal, it needn't be static. Here we take a signal as an argument which drives the value displayed.

(recurrent/defcomponent Hello
  [the-name-$]
  {:recurrent/dom-$
   (ulmus/map
     (fn [the-name]
       [:h1 {} (str "Hello " the-name)])
     the-name-$)})

We map over a signal, the-name-$, which provides the name to be printed. Any time the-name-$ changes a new dom object will be emitted and the component will rerender.

Sources

Components are provided with "sources" that allow the user to generate new signals based on external data. The DOM source is used to generate signals from events on the component's DOM. Let's see how this works. In the following example, we're going to display a button and a label indicating how many times the button has been clicked.

(recurrent/defcomponent ButtonClickCount
  []
  (let [count-$ (ulmus/reduce inc 0 ($ :recurrent/dom-$ "button" "click"))]
    {:recurrent/dom-$
      (ulmus/map
        (fn [count]
          [:div {:class "button-click-count"}
            [:button {} "Click Me!"]
            [:p {} (str "You've clicked " count " times.")]]) count-$)}))

Here, we create a signal called count-$. count-$ is a reduction against inc starting at 0.

($ :recurrent/dom-$ "button" "click")

generates a signal of events that occur when the button in the component is clicked.

$ is a special symbol used within the context of a defcomponent to access sources. The first argument is the name of the source, in this case :recurrent/dom-$. The addititional arguments are provided to the source for generating the signal.

The :recurrent/dom-$ source takes a CSS selector ("button" here), and an event, in this case "click". Components can only general signals for events sourced from inside their own DOM.

We'll see where sources come from shortly.

Instantiation

Let's imagine we have a text input component.

(recurrent/defcomponent Input
  [initial-value]
  (let [value-$ 
        (ulmus/start-with!
          initial-value
          (ulmus/map
            (fn [e] 
              (.-value (.-target e)))
            ($ :recurrent/dom-$ ".my-input" "input")))]

    {:value-$ value-$

     :recurrent/dom-$
     (ulmus/map
       (fn [v] 
         [:div {}
          [:input {:class "my-input"
	           :type "text"
		   :value v}]])
       value-$)}))

The Input component returns a signal value-$. This is built from it's oninput event looking at target.value in the event object. We may want to use this component from within others. There's another special symbol, !, used for instantiation. To create an instance of the input we do something like this

(let [input (! Input "FooBar")])

input is a map with the :recurrent/dom-$ and :value-$ keys defined in the componet. The component to be instantiated is the first argument to !. Additional arguments will be passed to the component. Above, initial-value is set to "FooBar".

Here's a more complete example.

(recurrent/defcomponent Main
  [message]
  (let [input (! Input "Partner")]
    {:recurrent/dom-$
     (ulmus/map
       (fn [[input-dom input-value]]
         [:div {}
          input-dom
          [:br]
          (str message " " input-value)])
       (ulmus/zip
         (:recurrent/dom-$ input)
         (:value-$ input)))}))

Drivers and the Main Loop

So far we've seen how sources allow for the creation of new signals from external sources (like DOM events). We've also seen how components can mutate external sources by returning signals (i.e., the way the :recurrent/dom-$ signal changes the actual DOM). These changes are mediated by drivers. The aforementioned facilities come from the dom driver included with Recurrent.

To start a recurrent reconciliation loop, and provide drivers to our components, we use the function recurrent.core/start!. start!'s arguments are the Component to instantiate, a map of drivers, followed by any additional arguments to the component. Here's an example:

(defn main!
  []
  (recurrent/start!
    Main
    {:recurrent/dom-$
     (recurrent.drivers.dom/render-into! "app")}))

This will instantiate the Main component. The dom emitted on the signal at :recurrent/dom-$ will be rendered into the div with id of "app".

Drivers' sources can be accessed at the key at which they are instantiated. i.e. we now have access to ($ :recurrent/dom-$) within Main and any other components instantiated from Main. The dom driver should always be instantiated at the :recurrent/dom-$ key, although that limitation may be removed in the future.

Recurrent ships with three drivers by default, dom, state, and http.

State Managment

The state driver facilitates signal-driven reducer based state management. When the state driver is instanated at a particular key and passed to start!, the current state will be provided as a signal at that key to the component.

(defn main!
 []
 (recurrent/start!
   Main
   {:recurrent/state-$
    (recurrent.drivers.state/create-store! {:some "state"})}))

The Main component can now access a signal of the current state with ($ :recurrent/state-$).

Main can also return a signal at :recurrent/state-$ of reducing functions. i.e. functions that accept a signal argument, the current state, and return the newly desired state. The driver will run these reductions as they're generated on the signal and update the source.

See recurrent-todo for a complete example.

Examples

Recurrent was used to build Konstellate. More information on Konstellate can be found at konstellate.io or it's repo.

A handful of examples can be found in the recurrent-examples repo and running here.

Status

Recurrent is beta quality and shouldn't be relied upon yet for mission critical applications.

Roadmap

Short Term

  • Documentation
    • Docstring and API
    • HTTP Driver
    • Creating new drivers
    • More examples
  • Spec
  • Performance & bug fixes

Longer Term

  • Potentially move drivers into seperate repos
  • Investigate alternative v-dom implementations
  • Abstract the FRP implementation
  • Add helper funcs/macros for common patterns

Thanks

The Recurrent logo was created by the eminently talented @the_stritt.

Recurrent was largely inspired by Cycle.js.

License

Copyright 2019 Jeremy Kross

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].