Reaxive.Rx
This module implements the combinator on reactive streams of events.
The functionality is closely modelled after Reactive Extensions and after ELM.
However, names of function follow the tradition of Elixir’s Enum
and
Stream
modules, if applicable.
See the test cases in rx_test.exs
for usage patterns.
Summary
all(rx, pred) | Returns |
any(rx, pred) | Returns |
as_text(rx) | This is a simple sink for events, which can be used for debugging event streams. It writes all events to standard out |
async(rx1) | The call to |
delayed_start(generator, id \\ "delayed_start", timeout \\ 5000) | The |
distinct(rx) | The |
distinct_until_changed(rx) | The |
drop(rx, n) |
|
drop_while(rx, pred) |
|
empty(timeout \\ 5000) | Creates the empty sequence of events. After a subscription, the sequence terminates immediately |
error(exception, timeout \\ 5000) | The |
eval(exp) | Evaluates a lazy expression, encoded in |
filter(rx, pred) | This function filters the event sequence such that only those
events remain in the sequence for which |
first(rx) | The first element of the event sequence. Does return the first scalar value and dispose the event sequence. The effect is similar to |
flat_map(rx, map_fun) | The |
generate(collection, delay \\ 0, timeout \\ 5000) | The |
lazy(expr) | This macros suspends an expression and replaces is with an |
map(rx, fun) | The |
merge(rxs) | |
merge(rx1, rx2) | Merges two or more event sequences in a non-deterministic order |
naturals(delay \\ 0, timeout \\ 5000) | Generates all naturals numbers starting with |
never() | The |
product(rx) | Multiplies all events of the sequence and returns the product as number |
reduce(rx, acc, reduce_fun) | This function considers the past events to produce new events.
Therefore this function is called in ELM |
return(value) | The |
start_with(prev_rx, collection) | The function |
stream(rx) | Converts a sequence of events into a (infinite) stream of events |
stream_observer(pid \\ self) | A simple observer function, sending tag and value as composed message to the
process |
sum(rx) | Sums up all events of the sequence and returns the sum as number |
take(rx, n) | This function produces only the first |
take_until(rx, pred) | Takes the first elements of the sequence until the predicate is true |
take_while(rx, pred) | Takes the first elements of the sequence while the predicate is true |
ticks(millis \\ 50) | Produces a infinite sequence of |
to_list(rx) | Converts the event sequence into a regular list. Requires that the sequence is finite, otherwise this call does not finish |
Types
t :: Observable.t
The basic type of Rx is an Observable
Functions
Specs:
- all(Observable.t, (any -> boolean)) :: boolean
Returns true
if pred
holds for all events in the sequence.
Examples
iex> alias Reaxive.Rx
iex> require Integer
iex> Rx.naturals |> Rx.take(10) |> Rx.map(&(1+&1*2)) |> Rx.all &Integer.is_odd/1
true
Specs:
- any(Observable.t, (any -> boolean)) :: boolean
Returns true
if pred
holds for at least one event in the sequence.
Examples
iex> alias Reaxive.Rx
iex> require Integer
iex> Rx.naturals |> Rx.take(10) |> Rx.any fn(x) -> x > 5 end
true
Specs:
- as_text(Observable.t) :: Observable.t
This is a simple sink for events, which can be used for debugging event streams. It writes all events to standard out.
Specs:
- async(Observable.t) :: Observable.t
The call to async
decouples two parts of an event sequence into asynchronuous
sequences. It is helpfull to explicitely introduce asynchronicity.
Examples
iex> alias Reaxive.Rx
iex> require Integer
iex> [1,2,3] |> Rx.generate |> Rx.filter( &Integer.is_odd/1 ) |>
iex> Rx.async |>
iex> Rx.map( &(&1 + 1)) |> Rx.to_list
[2, 4]
Specs:
- delayed_start((Observer.t -> any), String.t, non_neg_integer) :: Observable.t
The delayed_start
function starts a generator after the first
subscription has arrived. The generator
gets as argument rx
the
new creately Rx_Impl
and sends is internally encoded values via
Observer.on_next(rx, some_value)
All other functions on Rx_Impl
and Observer
, respectivley, can be called
within generator
as well.
If within timeout
milliseconds no subscriber has arrived, the
stream of events is stopped. This ensures that we get no memory leak.
Specs:
- distinct(Observer.t) :: Observer.t
The distinct
transformation is a filter, which only passes values that it
has not seen before. Since all distinct values has to be stored inside
the filter, its required memory can grow for ever, if an unbounded
sequence is used.
Examples
iex> alias Reaxive.Rx
iex> [1, 1, 2, 1, 2, 2, 3, 1] |> Rx.generate |> Rx.distinct |> Rx.to_list
[1, 2, 3]
Specs:
- distinct_until_changed(Observer.t) :: Observer.t
The distinct_until_changed
transformation is a filter, which filters
out all repeating values, such that only value changes remain
in the event sequence.
Examples
iex> alias Reaxive.Rx
iex> [1, 1, 2, 1, 2, 2, 3, 1] |> Rx.generate |> Rx.distinct_until_changed |> Rx.to_list
[1, 2, 1, 2, 3, 1]
Specs:
- drop(Observable.t, non_neg_integer) :: Observable.t
drop
filters out the first n
elements of the sequence.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(10) |> Rx.drop(5) |> Rx.to_list
[5, 6, 7, 8, 9]
Specs:
- drop_while(Observable.t, (any -> boolean)) :: Observable.t
drop_while
filters out the first elements while the predicate is true
.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(10) |> Rx.drop_while(&(&1 < 5)) |> Rx.to_list
[5, 6, 7, 8, 9]
Creates the empty sequence of events. After a subscription, the sequence terminates immediately.
Examples
iex> alias Reaxive.Rx
iex> Rx.empty |> Rx.stream |> Enum.to_list
[]
The error
function takes an in Elixir defined exception and generate a stream with the
exception as the only element.
Examples
iex> alias Reaxive.Rx
iex> me = self
iex> Rx.error(RuntimeError.exception("yeah")) |>
iex> Observable.subscribe(fn(t, x) -> me |> send {t, x} end) |> Runnable.run
iex> receive do x -> x end
{:on_error, %RuntimeError{message: "yeah"}}
Evaluates a lazy expression, encoded in Rx.Lazy
. Returns the argument
if it is not an Rx.Lazy
encoded
Specs:
- filter(Observable.t, (any -> boolean)) :: Observable.t
This function filters the event sequence such that only those
events remain in the sequence for which pred
returns true.
In Reactive Extensions, this function is called Where
.
Examples
iex> alias Reaxive.Rx
iex> require Integer
iex> Rx.naturals |> Rx.take(5) |> Rx.filter(&Integer.is_even/1) |> Rx.to_list
[0, 2, 4]
Specs:
- first(Observable.t) :: term
The first element of the event sequence. Does return the first scalar value and dispose the event sequence. The effect is similar to
rx |> Rx.stream |> Stream.take(1) |> Enum.fetch(0)
This function is not lazy, but evaluates eagerly and forces the subscription.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(5) |> Rx.first
0
iex> alias Reaxive.Rx
iex> Rx.return(3) |> Rx.first
3
Specs:
- flat_map(Observable.t, (any -> Observable.t)) :: Observable.t
The flat_map
function takes a mapping function and a sequence
of events. It applies the map_fun
to each source event. The map_fun
is a
function which must return a sequence of events (i.e. an Observable
). All
resulting sequences are then flattened, such that only one sequence of events
is returned from flat_map
. Since all sequences generated by the mapping
process are running concurrently to each other, the flattening process
does not ensure any ordering of the resulting event sequence.
In Reactive Extensions, this function is called SelectMany
.
Specs:
- generate(Enumerable.t, non_neg_integer, non_neg_integer) :: Observable.t
The generate
function takes a collection and generates for each
element of the collection an event. The delay between the events
is the second parameter. The delay also takes place before the
very first event.
This function is always a root in the net of communicating observables and does not depend on another observable.
This function can also be used with a lazy stream, such that unfolds and the like generate infininte many values. A typical example is the natural number sequence or the tick sequence
naturals = Rx.generate(Stream.unfold(0, fn(n) -> {n, n+1} end))
ticks = Rx.generate(Stream.unfold(:tick, fn(x) -> {x, x} end))
Examples
iex> alias Reaxive.Rx
iex> Rx.generate([1, 2, 3]) |> Rx.map(&(&1 * 2)) |> Rx.to_list
[2, 4, 6]
Specs:
- map(Observable.t, (... -> any)) :: Observable.t
The map
functions takes an observable rx
and applies function fun
to
each of its values.
In ELM, this function is called lift
, since it lifts a pure function into
a signal, i.e. into an observable.
In Reactive Extensions, this function is called Select
.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(5) |> Rx.map(&(2+&1)) |> Rx.stream |> Enum.to_list
[2, 3, 4, 5, 6]
Specs:
- merge([Observable.t]) :: Observable.t
Specs:
- merge(Observable.t, Observable.t) :: Observable.t
Merges two or more event sequences in a non-deterministic order.
The result sequences finishes after all sequences have finished without errors or immediately after the first error.
Examples
iex> alias Reaxive.Rx
iex> tens=Rx.naturals |> Rx.take(10)
iex> fives=Rx.naturals |> Rx.take(5)
iex> [tens, fives] |> Rx.merge() |> Rx.to_list |> Enum.sort
[0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 6, 7, 8, 9]
Specs:
- naturals(non_neg_integer, non_neg_integer) :: t
Generates all naturals numbers starting with 0
.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(5) |> Rx.stream |> Enum.to_list
[0, 1, 2, 3, 4]
Specs:
- never :: Observable.t
The never
function creates a sequence of events that never pushes anything.
Examples
iex> alias Reaxive.Rx
iex> Rx.never |> Observable.subscribe(Rx.stream_observer) |> Runnable.run
iex> receive do
iex> _ -> :got_something
iex> after 1_000
iex> -> :nothing
iex> end
:nothing
Specs:
- product(Observable.t) :: number
Multiplies all events of the sequence and returns the product as number
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(5) |> Rx.map(&(&1 +1)) |> Rx.product
120
iex> alias Reaxive.Rx
iex> 1..5 |> Rx.generate(1) |> Rx.product
120
Specs:
- reduce(Observable.t, any, (any, any -> any)) :: Observable.t
This function considers the past events to produce new events.
Therefore this function is called in ELM foldp
, folding over the past.
In Elixir, it is the convention to call the fold function reduce
, therefore
we stick to this convention.
The result of reduce is an event sequence with exactly one element. To get
the scalar value, apply function first
to it.
The reduce_fun
function is simple, applying the current event together with
the accumulator producing a new accumulator. Finally, the accumulator is
returned, when the source event stream is finished. The sum reducer could be
implemented as
def sum(rx) do
rx |> Rx.reduce(0, fn(x, acc) -> x + acc end)
end
For more complex reducing functionalities, see the Reaxive.Sync
module.
Specs:
- return(any) :: Observable.t
The return
function takes a value
and creates an event sequence
with exactly this value
and terminates afterwards.
It is essentially the same as
generate([value])
Examples:
iex> alias Reaxive.Rx
iex> Rx.return(3) |> Rx.stream |> Enum.to_list
[3]
iex> alias Reaxive.Rx
iex> Rx.return(5) |> Rx.stream |> Enum.to_list
[5]
Specs:
- start_with(Observable.t, Enumerable.t) :: Observable.t
The function start_with
takes a stream of events prev_rx
and a collection.
The resulting stream of events has all elements of colletion
,
followed by the events of prev_rx
.
Examples
iex> alias Reaxive.Rx
iex> Rx.generate([5]) |> Rx.start_with([0, 1, 2]) |> Rx.to_list
[0, 1, 2, 5]
Specs:
- stream(Observable.t) :: Enumerable.t
Converts a sequence of events into a (infinite) stream of events.
This operator is not lazy, but eager, as it forces the subscribe and therefore the evaluation of the subscription.
Examples
iex> alias Reaxive.Rx
iex> Rx.generate(1..5) |> Rx.stream |> Enum.to_list
[1, 2, 3, 4, 5]
Specs:
- stream_observer(pid) :: Observer.t
A simple observer function, sending tag and value as composed message to the
process pid
. The function sends the following messages:
{:on_next, value}
{:on_complete, nil}
{:on_error, error_value}
This function is used in various situations within Rx, in particular for implementing
the stream
combinator. You find its use also in the examples for never
.
Specs:
- sum(Observable.t) :: number
Sums up all events of the sequence and returns the sum as number.
Examples
iex> alias Reaxive.Rx iex> 1..5 |> Rx.generate(1) |> Rx.sum 15
iex> alias Reaxive.Rx iex> Rx.naturals |> Rx.map(&(&1 +1)) |> Rx.take(5) |> Rx.sum 15
Specs:
- take(Observable.t, non_neg_integer) :: Observable.t
This function produces only the first n
elements of the event sequence.
n
must be positive.
A negative n
would take elements from the back (the last n
elements.)
This can be achieved by converting the sequence into a stream and back again:
rx |> Rx.stream |> Stream.take(-n) |> Rx.generate
But it would not work if the sequence is infinite, for obvious reasons.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(10) |> Rx.to_list
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Specs:
- take_until(Observable.t, (any -> boolean)) :: Observable.t
Takes the first elements of the sequence until the predicate is true.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take_until(&(&1 > 5)) |> Rx.to_list
[0, 1, 2, 3, 4, 5]
Specs:
- take_while(Observable.t, (any -> boolean)) :: Observable.t
Takes the first elements of the sequence while the predicate is true.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take_while(&(&1 < 5)) |> Rx.to_list
[0, 1, 2, 3, 4]
Specs:
- ticks(non_neg_integer) :: Observable.t
Produces a infinite sequence of :tick
s, with a delay of millis
milliseconds
between each tick.
Examples
iex> alias Reaxive.Rx
iex> Rx.ticks |> Rx.take(5) |> Rx.to_list
[:tick, :tick, :tick, :tick, :tick]
Specs:
- to_list(Observable.t) :: [any]
Converts the event sequence into a regular list. Requires that the sequence is finite, otherwise this call does not finish.
Examples
iex> alias Reaxive.Rx
iex> Rx.naturals |> Rx.take(5) |> Rx.to_list
[0, 1, 2, 3, 4]
Macros
This macros suspends an expression and replaces is with an Rx.Lazy
thunk.