API
Reagents.jl is still work-in-progress.
Reagents
Reagents.Block
Reagents.CAS
Reagents.Computed
Reagents.Identity
Reagents.Map
Reagents.PostCommit
Reagents.Read
Reagents.Reagent
Reagents.Ref
Reagents.Retry
Reagents.Return
Reagents.Until
Reagents.Update
Reagents.WithNack
CompositionsBase.:⨟
Reagents.:&
Reagents.:|
Reagents.channel
Reagents.dissolve
Reagents.try!
Reagents.trysync!
Reagents
Reagents.Reagent
— TypeReagents.Reagent
Composable description for nonblocking and synchronous operations.
Reagents can be composed with ∘
although it is recommended to use the opposite composition operator ⨟
. A composed Reagent can be called, just like a function, to actually execute the side-effects.
Example
julia> using Reagents
julia> reagent = Reagents.Return(1) ⨟ Reagents.Map(string);
julia> reagent()
"1"
Reagents.CAS
— TypeReagents.CAS(ref::Reagents.Ref{T}, expected::T, desired::T)
A reagent for replacing the value in ref
from expected
to desired
. Multiple CAS reagents are committed atomically.
Example
julia> using Reagents
julia> ref1 = Reagents.Ref(111);
julia> ref2 = Reagents.Ref(222);
julia> reagent = Reagents.CAS(ref1, 111, -1) ⨟ Reagents.CAS(ref2, 222, -2);
julia> reagent();
julia> ref1[]
-1
julia> ref2[]
-2
Reagents.Computed
— TypeReagents.Computed(f)
Create a reagent dynamically with function f
which receives the output of the upstream reagent. The resulting reagent is composed with the downstream reagent.
Examples
julia> using Reagents
using Reagents: Read, CAS, Computed
julia> ref = Reagents.Ref(0);
julia> reagent = Read(ref) ⨟ Computed(old -> CAS(ref, old, old + 1));
julia> reagent();
julia> ref[]
1
julia> reagent();
julia> ref[]
2
Reagents.Identity
— TypeReagents.Identity()
The identity reagent. This is the identity element of ⨟
(hence of ∘
).
Examples
julia> using Reagents
julia> reagent = Reagents.Map(string) ⨟ Reagents.Identity();
julia> reagent(111)
"111"
Reagents.Map
— TypeReagents.Map(f)
Transform the output value of the upstream reagent by f
and pass it to the downstream reagent.
Examples
julia> using Reagents
julia> ref = Reagents.Ref(111);
julia> (Reagents.Read(ref) ⨟ Reagents.Map(string))()
"111"
Reagents.PostCommit
— TypeReagents.PostCommit(f)
Run f(x)
when the reagent successfully completed its reaction where x
is the output of the upstream reagent.
Examples
julia> using Reagents
julia> ref = Reagents.Ref(111);
julia> reagent = Reagents.Read(ref) ⨟ Reagents.PostCommit(x -> @show(x));
julia> reagent();
x = 111
Reagents.Read
— TypeReagents.Read(ref::Reagents.Ref)
A reagent that reads the value in ref
.
Example
julia> using Reagents
julia> ref = Reagents.Ref(111);
julia> reagent = Reagents.Read(ref);
julia> reagent()
111
Reagents.Return
— TypeReagents.Return(value)
Pass value
to the downstream reagent.
Examples
julia> using Reagents
julia> Reagents.Return(1)()
1
julia> ref = Reagents.Ref(111);
julia> (Reagents.Return(222) ⨟ Reagents.Update((old, inc) -> (old + inc, old), ref))()
111
julia> ref[]
333
Reagents.Until
— TypeReagents.Until(f, loop::Reagent)
Keep reacting with reagent while f
returns nothing
on the output of loop
. The reaction of loop
will be committed if f
returns nothing
but without the reaction of the reagent upstream to Until
; i.e., they are still put on hold. Once f
returns non-nothing
, the upstream reaction (from reagents before the loop
), the reaction of the loop
, and the downstream reactions are committed together, like other type of reagents.
Examples
julia> using Reagents
julia> ref1 = Reagents.Ref(10);
ref2 = Reagents.Ref(111);
julia> reagent = Reagents.Until(Reagents.Update((x, _) -> (x - 1, x), ref1)) do x
if x > 1
nothing
else
Some(x)
end
end ⨟ Reagents.Update((x, _) -> (x + 1, x), ref2);
julia> reagent()
111
julia> ref1[]
0
julia> ref2[]
112
Reagents.Update
— TypeReagents.Update(f, ref::Reagents.Ref{T})
Given a function of form (v::T, a) -> (w::T, b)
and a Reagents.Ref
holding a value of type T
, update its value to w
and pass the output b
to the downstream reagent. The function f
receives the value v
in ref
and the output of a
of the upstream reagent.
Example
julia> using Reagents
julia> ref = Reagents.Ref(0);
julia> add! = Reagents.Update((v, a) -> (v + a, v), ref);
julia> add!(1) # add 1 and return the old value
0
julia> add!(2)
1
julia> ref[]
3
Reagents.WithNack
— TypeReagents.WithNack(f)
Dynamically create a reagent with negative acknowledgement (nack) reagent.
Function f
takes a reagent nack
that is blocked until the reaction of this reagent is cancelled (i.e., another branch of |
is selected). Function f
must return a reagent.
Reagents.channel
— FunctionReagents.channel(A::Type, B::Type = A) -> (a2b, b2a)
Create a pair of swap endpoints for exchanging a value a
of type A
and a value b
of type B
synchronously.
The endpoints a2b
and b2a
are reagents.
Example
julia> using Reagents
julia> a2b, b2a = Reagents.channel(Int, Symbol);
julia> t = @async b2a(:hello);
julia> a2b(123)
:hello
julia> fetch(t)
123
Reagent Combinators
Reagents.:|
— Functionr1 | r2
The choice combinator. Invokes the reaction of one and exactly one reagent. The choice is left-biased; i.e., r1 | r2
tries r1
first and then try r2
.
Example
julia> using Reagents
julia> ref1 = Reagents.Ref(111);
julia> ref2 = Reagents.Ref(222);
julia> r1 = Reagents.CAS(ref1, -1, 0); # will fail (wrong expected old value)
julia> r2 = Reagents.CAS(ref2, 222, 333); # will succeed
julia> (r1 | r2)();
julia> ref1[]
111
julia> ref2[]
333
Reagents.:&
— Functionr1 & r2
The pairing combinator. Both reagents r1
and r2
are executed and the downstream reagent receives the tuple (y1, y2)
where y1
is the output of r1
and y2
is the output of r2
.
Example
julia> using Reagents
julia> ref1 = Reagents.Ref(111);
julia> ref2 = Reagents.Ref(222);
julia> r1 = Reagents.Read(ref1);
julia> r2 = Reagents.Read(ref2);
julia> (r1 & r2)()
(111, 222)
CompositionsBase.:⨟
— Functionr1 ⨟ r2
r2 ∘ r1
opcompose(r1, r2)
compose(r2, r1)
The operator ⨟
(\bbsemi
) sequences reagents. The composition r1 ⨟ r2
means to invoke r1
and also r2
with the output of r1
. Note that the entire reaction is still done atomically.
The syntaxes r1 ⨟ r2
and r2 ∘ r1
are equivalent. It is recommended to use r1 ⨟ r2
to clarify the top-to-down data flow when the reagent definition spans multiple lines. However, knowing that r1 ⨟ r2
is equivalent to r2 ∘ r1
can be useful for remember that, in the reaction (r2 ∘ r1)(x)
, the reagent r1
is the one that sees the input x
.
opcompose
and compose
are ASCII aliases of ⨟
and ∘
, respectively.
Example
julia> using Reagents
julia> ref = Reagents.Ref(111);
julia> r1 = Reagents.Read(ref);
julia> r2 = Reagents.Map(string);
julia> (r1 ⨟ r2)()
"111"
Reaction failures
Reagents.Block
— TypeReagents.Block()
A value indicating that the reaction is blocked.
Reagents such as Reagents.Computed
and Reagents.Map
can return this value to indicate that other branches of the choice combinator |
should be used.
Reagents.Retry
— TypeReagents.Retry
A value indicating that the reaction should be retried.
Misc
Reagents.Ref
— TypeReagents.Ref{T}()
Reagents.Ref{T}(value::T)
Create a reference storing a value of type T
.
Use reagents such as Reagents.Update
, Reagents.CAS
, and Reagents.Read
to manipulate the value.
Example
julia> using Reagents
julia> ref = Reagents.Ref{Int}();
julia> ref[] = 111;
julia> ref[]
111
julia> Reagents.try!(Reagents.CAS(ref, 111, 222)) !== nothing
true
julia> ref[]
222
Reagents.dissolve
— FunctionReagents.dissolve(catalyst::Reagent)
Register catalyst
as a persistent reagent that helps other reaction.
The reagent catalyst
must include a blocking reagent (i.e., Reagents.channel
).
For more information, see Catalysts section in the manual.
Examples
julia> using Reagents
julia> send1, receive1 = Reagents.channel(Int, Nothing)
send2, receive2 = Reagents.channel(Char, Nothing)
sendall, receiveall = Reagents.channel(Tuple{Int,Char}, Nothing);
julia> catalyst = (receive1 & receive2) ⨟ sendall;
julia> Reagents.dissolve(catalyst);
julia> @sync begin
@async send1(1)
@async send2('a')
receiveall()
end
(1, 'a')
Reagents.try!
— FunctionReagents.try!(reagent::Reagent, [value = nothing])
Invoke reagent
with value
. If it succeeds with an output
, return Some(output)
. If it fails to react, return nothing
.
It is guaranteed to return even if reagent
is unsatisfiable (e.g., Reagents.CAS(Reagents.Ref(0), 1, 2)
).
Reagents.trysync!
— FunctionReagents.trysync!(reagent::Reagent, [value = nothing]) -> Some(output) or nothing
Invoke synchronizing reagent
with value
. If it succeeds with an output
, return Some(output)
. If it is blocked, return nothing
.
This function is guaranteed to return if the reagent
eventually succeeds or synchronizes. Supplying unsatisfiable reagent
(e.g., Reagents.CAS(Reagents.Ref(0), 1, 2)
) is unsupported (it is likely to deadlock).