View Source Reactive (Reactive State v0.2.0)

Module to manage reactive state by using GenServer processes ("reactive process" from here on) to manage each piece of state and its relationships to other reactive processes.

Installation

The package can be installed by adding reactive_state to your list of dependencies in mix.exs:

def deps do
  [
    {:reactive_state, "~> 0.2.0"}
  ]
end

Working with data directly with Reactive.Ref

iex> ref = Ref.new(0) #PID<0.204.0>
iex> Ref.get(ref) # or Ref.get(ref)
0
iex> Ref.set(ref, 1)
:ok
iex> Ref.get(ref)
1

Reactive Block

iex> use Reactive
iex> ref = Ref.new(2)
iex> ref_squared = reactive do
...>   get(ref) ** 2
...> end
iex> Reactive.get(ref_squared)
4
iex> Ref.set(ref, 3)
iex> Reactive.get(ref_squared)
9

Conditional Branches

iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> if_false = Ref.new(1)
iex> if_true = Ref.new(2)
iex> toggle = Ref.new(false)
iex> computed = reactive do
...>   if get(toggle) do
...>     get(if_true)
...>   else
...>     get(if_false)
...>   end
...> end
iex> Reactive.get(computed)
1
iex> Ref.set(toggle, true)
:ok
iex> Reactive.get(computed)
2
iex> # Now, updating `if_false` will not require a recomputation
iex> Ref.set(if_false, 0)
:ok
iex> Reactive.get_cached(computed)
2
iex> # Updating `if_true` will require a recomputation
iex> Ref.set(if_true, 3)
:ok
iex> Reactive.get_cached(computed)
:stale
iex> Reactive.get(computed)
3

Supervisor

By default, new reactive processes will be started under the DynamicSupervisor Reactive.Supervisor, if that supervisor exists. If not, it will be created under the current process.

To override this behavior, pass the supervisor keyword arg during process creation:

value = Ref.new(0, supervisor: MyApp.Supervisor)
ref = reactive supervisor: MyApp.Supervisor do
  get(value) + 1
end

These examples include a method which automatically starts the supervisor for you (Reactive.Supervisor.ensure_started), but you should set it up in your own supervision tree.

Process Restarting

If a reactive process has been killed for any reason, it will be restarted upon a Reactive.get or Ref.get call:

iex> Reactive.Supervisor.ensure_started()
iex> ref = Ref.new(0)
iex> DynamicSupervisor.terminate_child(Reactive.Supervisor, ref)
iex> Ref.get(ref)
0

Garbage Collection

The default garbage collection strategy is to kill any processes that were not accessed through a Reactive.get or Ref.get call between GC calls:

iex> Reactive.Supervisor.ensure_started()
iex> ref = Ref.new(0)
iex> Reactive.Supervisor.gc()
iex> nil == Reactive.resolve_process(ref)

Reactive processes can be protected with the gc option:application

iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> ref = Ref.new(0, gc: false)
iex> Reactive.Supervisor.gc()
iex> ^ref = Reactive.resolve_process(ref)

iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> ref = reactive gc: false do
...>    # some expensive computation
...> end
iex> Reactive.Supervisor.gc()
iex> ^ref = Reactive.resolve_process(ref)

Proactive Process

Proactive reactive processes will not trigger immediately after a dependency changes; they must triggered with a call to Reactive.Supervisor.trigger_proactive

iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> num = Ref.new(0)
iex> ref =
...>   reactive proactive: true do
...>     get(num) + 1
...>   end
iex> Reactive.get_cached(ref)
1
iex> Ref.set(num, 1)
iex> Reactive.Supervisor.trigger_proactive()
iex> Reactive.get_cached(ref)
2

Summary

Functions

Returns a specification to start this module under a supervisor.

Retrieve the state of a reactive process

Retrieve the cached state of a reactive process, or :stale if it has not been computed or is stale

Create a reactive process using a method

Syntatic sugar for creating reactive blocks

Replace a reactive process's computation method

Functions

Returns a specification to start this module under a supervisor.

See Supervisor.

Retrieve the state of a reactive process

Example

iex> ref = Reactive.new(fn _ -> 0 end)
iex> Reactive.get(ref)
0

Retrieve the cached state of a reactive process, or :stale if it has not been computed or is stale

Example

iex> use Reactive
iex> ref = reactive do
...>   0
...> end
iex> Reactive.get_cached(ref)
:stale
iex> Reactive.get(ref)
0
iex> Reactive.get_cached(ref)
0

Create a reactive process using a method

iex> use Reactive
iex> ref = Ref.new(2)
iex> ref_squared = Reactive.new(fn call_id ->
...>   Reactive.get(ref, call_id: call_id) ** 2
...> end)
iex> Reactive.get(ref_squared)
4
Link to this macro

reactive(opts)

View Source (macro)

Syntatic sugar for creating reactive blocks

iex> use Reactive
iex> ref = Ref.new(2)
iex> ref_squared = reactive do
...>   get(ref) ** 2
...> end #PID<0.204.0>
iex> Reactive.get(ref_squared)
4
iex> Ref.set(ref, 3)
iex> Reactive.get(ref_squared)
9

Using the reactive macro in this way is roughly equivalent to:

iex> use Reactive
iex> ref = Ref.new(2)
iex> ref_squared = Reactive.new(fn call_id ->
...>   Reactive.get(ref, call_id: call_id) ** 2
...> end)
iex> Reactive.get(ref_squared)
4
iex> Ref.set(ref, 3)
iex> Reactive.get(ref_squared)
9
Link to this macro

reactive(opts, list)

View Source (macro)
Link to this function

resolve_process(pid, opts \\ [])

View Source

Replace a reactive process's computation method

iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> ref = reactive do
...>   0
...> end
iex> Reactive.get(ref)
0
iex> Reactive.set(ref, fn _ -> 1 end)
:ok
iex> Reactive.get(ref)
1