View Source Reactive (Reactive State v0.2.4)
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.4"}
]
endReactive Block
To automatically import the reactive/1 and reactive/2 macros, you can use use Reactive which is the equivalent of:
import Reactive, only: [reactive: 1, reactive: 2]
alias Reactive.RefExample usage:
iex> use Reactive
iex> ref = reactive(do: 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)
9To set options at the module level, you can pass options, for example:
iex> defmodule ReactiveExample do
...> use Reactive, reactive: :reactive_protected, ref: :ref_protected, opts: [gc: false]
...>
...> def run do
...> value = ref_protected(0)
...> computed = reactive_protected do
...> get(value) + 1
...> end
...> {Ref.get(value), Ref.get(computed)}
...> end
...> end
iex>
iex> ReactiveExample.run()
{0, 1}
Working with data directly with Reactive.Ref
iex> alias 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)
1Supervisor
By default, new reactive processes will be linked to the current process.
To override this behavior, pass the supervisor keyword arg with the name of your DynamicSupervisor during process creation:
value = Ref.new(0, supervisor: MyApp.Supervisor)
computed = reactive supervisor: MyApp.Supervisor do
get(value) + 1
endYou can also pass default options like this:
use Reactive, ref: :ref, opts: [supervisor: MyApp.Supervisor]
...
value = ref(0)
computed = reactive do
get(value) + 1
endProcess Restarting
If a reactive process has been killed for any reason, it will be restarted upon a Reactive.get or Ref.get call:
iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> ref = Ref.new(0)
iex> DynamicSupervisor.terminate_child(Reactive.Supervisor, ref)
iex> Ref.get(ref)
0Garbage 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> use Reactive
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:
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)Named Process
You can name a reactive process using the name option:
iex> use Reactive
iex> Reactive.Supervisor.ensure_started()
iex> Ref.new(0, name: MyApp.Value)
iex> reactive name: MyApp.Computed do
...> get(MyApp.Value) + 1
...> end
iex> Ref.get(MyApp.Value)
0
iex> Reactive.get(MyApp.Computed)
1Proactive 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
Create a reactive process with options
Find a reactive process from a pid or alias.
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
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)
9Using the reactive macro in this way is roughly equivalent to:
iex> use Reactive
iex> ref = Ref.new(2)
iex> ref_squared = Reactive.new(fn from ->
...> Reactive.get(ref, from: from) ** 2
...> end)
iex> Reactive.get(ref_squared)
4
iex> Ref.set(ref, 3)
iex> Reactive.get(ref_squared)
9
Create a reactive process with options:
reactive name: MyApp.SomeValue, gc: false, proactive: true, supervisor: MyApp.DynamicSupervisor do
# ...
end
Find a reactive process from a pid or alias.
iex> use Reactive
iex> pid = Ref.new(0, name: MyApp.Value)
iex> true = Reactive.resolve_process(MyApp.Value) == Reactive.resolve_process(pid)You can ensure a process will be returned by passing the create: true option
Reactive.resolve_process(pid, create: true)
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