module Froc:Functional reactive programmingsig..end
Froc implements functional reactive programming in the style of
FrTime / Flapjax (but typed of course). It uses the dynamic
dependency graph of Acar et al. (self-adjusting
computation). Behaviors are presented as monadic values, using
ideas from Lwt.
A behavior is a value that can change over time, but is defined
at all times. An event is defined only at particular instants
in time, with a possibly different value at each instant; when an
event takes value v we say it occurs with value v or fires v. Values are sent to an event using the associated event sender. A behavior or an event is a signal when we don't
care to specify which one it is, and we say a signal "changes" to
mean that it changes if it is a behavior or occurs if it is an
event.
Most of the functions in Froc create a new signal from one or
more existing signals. The output signals are dependents of the
input signals and the inputs are the dependencies of the
outputs. When a signal changes, its dependents are updated
(according to some update function specific to the signal). The
dependencies of a signal are updated before the signal is updated,
so that the update function sees a consistent view of the
dependencies. Signals may have listeners which are notified
when the signal changes. Listeners are like dependents but don't
compute new signals; they are just functions, called for their
effect.
When an event is sent (with send), an update cycle begins,
during which the transitive dependents of the initial event are
updated, and their listeners notified. It is not allowed to call
send again until the update cycle is finished, but
send_deferred may be called, which queues the event. Queued
events are processed (each in a new update cycle) following each
update cycle until the queue is empty. Events occurring in the same
update cycle are simultaneous.
The body of a listener or update function (such as the function
passed to bind) delimits a dynamic scope, which is governed by the signals to which it is attached. When a signal
changes, listeners and dependents attached within the dynamic
scopes it governs are detached, and any attached cleanup
functions are called. This cleanup allows unneeded values to be
garbage collected, and also prevents them from being recomputed
unnecessarily or erroneously. Dynamic scopes are nested; cleaning
up an enclosing scope cleans up its enclosed scopes. Attachments
created outside any dynamic scope can only be detached by calling
init.
Ordinarily, the entirety of a dynamic scope is cleaned up when its
governing signal changes. A dynamic scope may be partially cleaned
up using a memo function (see memo). It is possible (though
not encouraged) to hold a reference to a signal (by storing it in a
ref, for instance) after it has been detached from its
dependencies; a detached signal is not updated when its
dependencies change.
Most functions returning behaviors take an optional eq argument,
which gives an equality function on the value of the resulting
behavior. A behavior's dependents are only updated when the
behavior is updated with a value which is not equal (according to
the equality function) to the old value. The default equality holds
if the values compare to 0 (incomparable values are always not
equal). It is encouraged that behaviors of the same type always be
given the same equality.
type 'a result =
| |
Value of |
| |
Fail of |
'a or exception.type cancel
type +'a behavior
'a.val return : 'a -> 'a behaviorreturn v is a constant behavior with value v.val fail : exn -> 'a behaviorfail e is a constant behavior that fails with the exception e.val bind : ?eq:('a -> 'a -> bool) ->
'b behavior -> ('b -> 'a behavior) -> 'a behaviorbind b f behaves as f applied to the value of b. If b
fails, bind b f also fails, with the same exception. The update
function f delimits a dynamic scope governed by b.val (>>=) : 'a behavior -> ('a -> 'b behavior) -> 'b behaviorb >>= f is an alternative notation for bind b f.val blift : ?eq:('a -> 'a -> bool) -> 'b behavior -> ('b -> 'a) -> 'a behaviorblift b ?eq f is equivalent to bind b (fun v -> return ?eq (f v)),
but is slightly more efficient.val lift : ?eq:('a -> 'a -> bool) -> ('b -> 'a) -> 'b behavior -> 'a behaviorlift ?eq f b is equivalent to blift b ?eq f; it can be
partially applied to lift a function to the monad without yet
binding it to a behavior.val sample : 'a behavior -> 'asample b returns the current value of b, or raises b's
exception if it is failed.val sample_result : 'a behavior -> 'a resultsample but returns a result.val catch : ?eq:('a -> 'a -> bool) ->
(unit -> 'a behavior) -> (exn -> 'a behavior) -> 'a behaviorcatch bf f behaves the same as bf() if bf() succeeds. If
bf() fails with some exception e, catch bf f behaves as f
e. The function f delimits a dynamic scope governed by bf().val catch_lift : ?eq:('a -> 'a -> bool) ->
(unit -> 'a behavior) -> (exn -> 'a) -> 'a behaviorcatch_lift bf ?eq f is equivalent to catch bf (fun e -> return
?eq (f e)), but is slightly more efficient.val try_bind : ?eq:('a -> 'a -> bool) ->
(unit -> 'b behavior) ->
('b -> 'a behavior) -> (exn -> 'a behavior) -> 'a behaviortry_bind bf f g behaves as bind (bf()) f if bf()
succeeds. If bf() fails with exception e, try_bind b f g
behaves as g e. The functions f and g each delimit a
dynamic scope governed by bf().val try_bind_lift : ?eq:('a -> 'a -> bool) ->
(unit -> 'b behavior) -> ('b -> 'a) -> (exn -> 'a) -> 'a behaviortry_bind_lift bf ?eq f g is equivalent to try_bind bf (fun v ->
return ?eq (f v)) (fun e -> return ?eq (g e)), but is slightly
more efficient.val join_b : ?eq:('a -> 'a -> bool) -> 'a behavior behavior -> 'a behaviorjoin_b b behaves as whichever behavior is currently the value of b.val fix_b : ?eq:('a -> 'a -> bool) ->
('a behavior -> 'a behavior) -> 'a behaviorfix_b bf returns bf b' where b' behaves like bf b', but
delayed one update cycle.val notify_b : ?now:bool -> 'a behavior -> ('a -> unit) -> unitnotify_b b f adds f as a listener for b, which is called
whenever b changes. When b fails the listener is not
called. The notification is cancelled when the enclosing dynamic
scope is cleaned up.
The listener is called immediately with the current value of the
behavior, unless now is false. The function f delimits a
dynamic scope governed by b.
val notify_b_cancel : ?now:bool -> 'a behavior -> ('a -> unit) -> cancelnotify_b, and returns a cancel handle (the notification
is still cancelled when the enclosing dynamic scope is cleaned up).val notify_result_b : ?now:bool -> 'a behavior -> ('a result -> unit) -> unitnotify_b but the listener is called with a result when
the value changes or when the behavior fails.val notify_result_b_cancel : ?now:bool -> 'a behavior -> ('a result -> unit) -> cancelnotify_result_b, and returns a cancel handle (the
notification is still cancelled when the enclosing dynamic scope
is cleaned up).val hash_behavior : 'a behavior -> intHashtbl.hash is not appropriate
because behaviors contain mutable data.type +'a event
'a.type -'a event_sender
'a.val make_event : unit -> 'a event * 'a event_sender'a.val never : 'a eventval notify_e : 'a event -> ('a -> unit) -> unitnotify_e e f adds f as a listener for e, which is called
with v whenever e occurs with value v. When a failure
occurs the listener is not called. The notification is cancelled
when the enclosing dynamic scope is cleaned up.
The function f delimits a dynamic scope governed by b.
val notify_e_cancel : 'a event -> ('a -> unit) -> cancelnotify_e, and returns a cancel handle (the notification
is still cancelled when the enclosing dynamic scope is cleaned up).val notify_result_e : 'a event -> ('a result -> unit) -> unitnotify_e but the listener is called with a result when
a value or a failure is sent.val notify_result_e_cancel : 'a event -> ('a result -> unit) -> cancelnotify_result_e, and returns a cancel handle (the
notification is still cancelled when the enclosing dynamic scope
is cleaned up).val send : 'a event_sender -> 'a -> unitsend s v sends the value v to the associated event e, so
e occurs with value v.val send_exn : 'a event_sender -> exn -> unitsend_exn s x sends the failure x to the associated event e,
so e occurs with failure x.val send_result : 'a event_sender -> 'a result -> unitsend_result s r sends the result r to the associated event
e, so e occurs with result r.val send_deferred : 'a event_sender -> 'a -> unitsend_deferred s v enqueues a call to send s v for a future
update cycle.val send_exn_deferred : 'a event_sender -> exn -> unitsend_exn_deferred s x enqueues a call to send_exn s x for a
future update cycle.val send_result_deferred : 'a event_sender -> 'a result -> unitsend_result_deferred s r enqueues a call to send_result s r
for a future update cycle.val next : 'a event -> 'a eventnext e passes on only the next occurence of e; subsequent
occurrences are dropped.val merge : 'a event list -> 'a eventmerge es occurs whenever any of the events in es occurs. If
more than one of the es occurs simultaneously, the earliest one
in the list is passed on.val map : ('a -> 'b) -> 'a event -> 'b eventmap f e fires f v whenever e fires v. The function f
delimits a dynamic scope governed by e.val map2 : ('a -> 'b -> 'c) -> 'a event -> 'b event -> 'c eventmap2 f e1 e2 fires f v1 v2 whenever e1 and e2 fire v1
and v2 simultaneously. The function f delimits a dynamic
scope governed by e1 and e2.val filter : ('a -> bool) -> 'a event -> 'a eventfilter p e is an event that fires v whenever e fires v
and p v is true. The function p delimits a dynamic scope
governed by e.val collect : ('a -> 'b -> 'a) -> 'a -> 'b event -> 'a eventcollect f b e is an event that maintains an internal state s
(initialized to b); whenever e fires v, s' becomes f s
v, the event fires s', and s' becomes the new internal
state. The function f delimits a dynamic scope governed by e.
Special care must be taken when using collect with behavior- or
event-valued events. The dynamic scope delimited by f is
cleaned up on each occurrence of e; any signals or created in
f become detached on the next occurrence, so it is easy to wind
up with detached signals in s.
This cleanup may be controlled through the use of memo.
val join_e : 'a event event -> 'a eventjoin_e ee occurs whenever the event which last occurred on ee
occurs.val fix_e : ('a event -> 'a event) -> 'a eventfix_e ef returns ef e' where e' is an event that occurs
whenever ef e' occurs, but in the next update cycle.val hash_event : 'a event -> intHashtbl.hash is not appropriate
because events contain mutable data.val switch : ?eq:('a -> 'a -> bool) ->
'a behavior -> 'a behavior event -> 'a behaviorswitch b e behaves as b until e occurs, then behaves as the
last value of e.val until : ?eq:('a -> 'a -> bool) ->
'a behavior -> 'a behavior event -> 'a behavioruntil b e behaves as b until e occurs with value b', then
behaves as b'.val hold : ?eq:('a -> 'a -> bool) -> 'a -> 'a event -> 'a behaviorhold v e takes on the last value which occurred on e, or
v if e has not yet occurred (since hold was called).val hold_result : ?eq:('a -> 'a -> bool) -> 'a result -> 'a event -> 'a behaviorhold but initialized with a result.val changes : 'a behavior -> 'a eventchanges b occurs with the value of b whenever b changes.val when_true : bool behavior -> unit eventwhen_true b fires whenever b becomes true.val count : 'a event -> int behaviorcount e takes on the number of times e has occurred (since
count was called).val make_cell : 'a -> 'a behavior * ('a -> unit)make_cell v returns a behavior (with initial value v) and a
setter function which changes the behavior's value. The setter
enqueues the update for a future update cycle, so it may be used
freely.val init : unit -> unitval no_cancel : cancelval cancel : cancel -> unitval cleanup : (unit -> unit) -> unitcleanup f attaches f to the enclosing dynamic scope, so it is
called when the scope is cleaned up. This is useful for cleaning
up external resources, such as GUI event handlers.val memo : ?size:int ->
?hash:('a -> int) -> ?eq:('a -> 'a -> bool) -> unit -> ('a -> 'b) -> 'a -> 'bmemo f creates a memo function f' from f. When f' x is
called from within an update function, there may be either a hit
or a miss. A hit happens when some f' x' was called in the
previous run of the update function, when eq x x', and no later
call has already hit (that is, hits must happen in the same order
as the calls happened in the previous run). On a miss, f' x
calls f x in a new dynamic scope, and stores its value for
possible reuse. On a hit, f' x returns the value of the
previous call, and any updates necessary to make the value
consistent are executed; the dynamic scope of the previous call
is not cleaned up (so that the value remains attached to its
dependencies).
The main point of memo is to avoid needless recomputation in
cases where a computation is governed by some signal but does not
actually use the signal's value. For example, in
let g = memo () fun x -> ... in
b >>= fun _ -> g 7
the returned behavior is indifferent to the value of
b. Without memo it would be recomputed every time b
changes; with memo it is computed only the first time.
Because the dynamic scope of the previous call is not cleaned up
on a memo hit, memo can be used purely to protect signals and
listeners from being detached when their governing signals
change. See the quickhull example for an instance of this use.
The unit argument makes it possible to memoize a recursive function, using the following idiom:
let m = memo () in (* creates the memo table *)
let rec f x = ... m f y in
let f x = m f x
The default hash function is not appropriate for behaviors and
events (since they contain mutable data); hash_behavior and
hash_event should be used instead.
val set_exn_handler : (exn -> unit) -> unitval set_debug : (string -> unit) -> unitval bind2 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior -> ('b -> 'c -> 'a behavior) -> 'a behaviorval blift2 : ?eq:('a -> 'a -> bool) ->
'b behavior -> 'c behavior -> ('b -> 'c -> 'a) -> 'a behaviorval lift2 : ?eq:('a -> 'a -> bool) ->
('b -> 'c -> 'a) -> 'b behavior -> 'c behavior -> 'a behaviorval bind3 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior -> ('b -> 'c -> 'd -> 'a behavior) -> 'a behaviorval blift3 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior -> ('b -> 'c -> 'd -> 'a) -> 'a behaviorval lift3 : ?eq:('a -> 'a -> bool) ->
('b -> 'c -> 'd -> 'a) ->
'b behavior -> 'c behavior -> 'd behavior -> 'a behaviorval bind4 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
('b -> 'c -> 'd -> 'e -> 'a behavior) -> 'a behaviorval blift4 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior -> ('b -> 'c -> 'd -> 'e -> 'a) -> 'a behaviorval lift4 : ?eq:('a -> 'a -> bool) ->
('b -> 'c -> 'd -> 'e -> 'a) ->
'b behavior ->
'c behavior -> 'd behavior -> 'e behavior -> 'a behaviorval bind5 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior ->
('b -> 'c -> 'd -> 'e -> 'f -> 'a behavior) -> 'a behaviorval blift5 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior -> ('b -> 'c -> 'd -> 'e -> 'f -> 'a) -> 'a behaviorval lift5 : ?eq:('a -> 'a -> bool) ->
('b -> 'c -> 'd -> 'e -> 'f -> 'a) ->
'b behavior ->
'c behavior ->
'd behavior -> 'e behavior -> 'f behavior -> 'a behaviorval bind6 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior ->
'g behavior ->
('b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'a behavior) -> 'a behaviorval blift6 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior ->
'g behavior ->
('b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'a) -> 'a behaviorval lift6 : ?eq:('a -> 'a -> bool) ->
('b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'a) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior -> 'f behavior -> 'g behavior -> 'a behaviorval bind7 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior ->
'g behavior ->
'h behavior ->
('b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'h -> 'a behavior) ->
'a behaviorval blift7 : ?eq:('a -> 'a -> bool) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior ->
'g behavior ->
'h behavior ->
('b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'h -> 'a) -> 'a behaviorval lift7 : ?eq:('a -> 'a -> bool) ->
('b -> 'c -> 'd -> 'e -> 'f -> 'g -> 'h -> 'a) ->
'b behavior ->
'c behavior ->
'd behavior ->
'e behavior ->
'f behavior -> 'g behavior -> 'h behavior -> 'a behaviorval bindN : ?eq:('a -> 'a -> bool) ->
'b behavior list -> ('b list -> 'a behavior) -> 'a behaviorval bliftN : ?eq:('a -> 'a -> bool) ->
'b behavior list -> ('b list -> 'a) -> 'a behaviorval liftN : ?eq:('a -> 'a -> bool) ->
('b list -> 'a) -> 'b behavior list -> 'a behavior