Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

doc rewrite

  • Loading branch information...
commit 6996e97ab687469bbad544461d8a19868ffdac34 1 parent dad62d9
Jake Donham authored
Showing with 307 additions and 170 deletions.
  1. +259 −136 src/froc/froc.mli
  2. +48 −34 src/froc/froc_sa.mli
View
395 src/froc/froc.mli
@@ -23,48 +23,83 @@
{2 Overview}
[Froc] implements functional reactive programming in the style of
- FrTime / Flapjax (but typed). It uses the dynamic dependency graph
- of Acar et al. (self-adjusting computation). Behaviors are
- presented as monadic values, using ideas from [Lwt].
-
- A {e behavior} is a monadic value that can change over
- time. Binding a behavior causes the binder to be made a dependency
- of the behavior, and to be re-executed when the behavior changes.
-
- An {e event} is a channel over which values may be sent (using the
- associated {e event_sender}). Listeners on the channel are notified
- when an event occurs (i.e. a value is sent on the channel).
-
- Sent events are queued; after each event in the queue is sent and
- its listeners notified, the dependency graph is processed to update
- any affected behaviors in a consistent way.
-
- When a dependency of a behavior must be re-executed, all resources
- (i.e. binds and notifies) used in the previous execution are
- released, and all cleanup functions set in the previous execution
- are run (see [cleanup]).
+ 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 {e behavior} is a value that can change over time, but is defined
+ at all times. An {e event} is defined only at particular instants
+ in time, with a possibly different value at each instance; when an
+ event takes value [v] we say it {e occurs with value} [v] or {e
+ fires} [v]. Values are sent to an event using the associated {e
+ event sender}. A behavior or an event is a {e 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 {e dependents} of the
+ input signals and the inputs are the {e 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 {e listeners} which are {e 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 {e 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 {e simultaneous}.
+
+ The body of a listener or update function (such as the function
+ passed to [bind]) delimits a {e dynamic scope}, which is {e
+ 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 {e 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.
*)
-val init : unit -> unit
- (** Initialize the library. Must be called before any other function. *)
+(** Type of values of type ['a] or exception. *)
+type 'a result = Value of 'a | Fail of exn
type cancel
(** Type of handles to listener registrations. *)
-val no_cancel : cancel
- (** Dummy cancel. *)
-
-val cancel : cancel -> unit
- (** Cancels a listener registration using the given handle. *)
-
(** {2 Behaviors} *)
type 'a behavior
(** Type of behaviors of type ['a]. *)
-(** Type of values of type ['a] or exception. *)
-type 'a result = Value of 'a | Fail of exn
-
val return : 'a -> 'a behavior
(**
[return v] is a constant behavior with value [v].
@@ -78,10 +113,8 @@ val fail : exn -> 'a behavior
val bind : ?eq:('b -> 'b -> bool) -> 'a behavior -> ('a -> 'b behavior) -> 'b behavior
(**
[bind b f] behaves as [f] applied to the value of [b]. If [b]
- fails, [bind b f] also fails, with the same exception.
-
- When the value of a behavior changes, all functions [f] bound to
- it are re-executed.
+ 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 behavior
@@ -91,8 +124,8 @@ val (>>=) : 'a behavior -> ('a -> 'b behavior) -> 'b behavior
val blift : ?eq:('b -> 'b -> bool) -> 'a behavior -> ('a -> 'b) -> 'b behavior
(**
- [blift b ?eq f] is equivalent to [bind b (fun v -> return ?eq (f
- v))], but is slightly more efficient.
+ [blift b ?eq f] is equivalent to [bind b (fun v -> return ?eq (f v))],
+ but is slightly more efficient.
*)
val lift : ?eq:('b -> 'b -> bool) -> ('a -> 'b) -> 'a behavior -> 'b behavior
@@ -115,26 +148,28 @@ val sample_result : 'b behavior -> 'b result
val catch : ?eq:('a -> 'a -> bool) -> (unit -> 'a behavior) -> (exn -> 'a behavior) -> 'a behavior
(**
- [catch b f] behaves the same as [b()] if [b()] succeeds. If [b()]
- fails with some exception [e], [catch b f] behaves as [f e].
+ [catch 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 behavior
(**
- [catch_lift b ?eq f] is equivalent to [catch b (fun e -> return
+ [catch_lift bf ?eq f] is equivalent to [catch bf (fun e -> return
?eq (f e))], but is slightly more efficient.
*)
val try_bind : ?eq:('b -> 'b -> bool) -> (unit -> 'a behavior) -> ('a -> 'b behavior) -> (exn -> 'b behavior) -> 'b behavior
(**
- [try_bind b f g] behaves as [bind (b()) f] if [b()] succeeds. If
- [b()] fails with exception [e], [try_bind b f g] behaves as [g
- e].
+ [try_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:('b -> 'b -> bool) -> (unit -> 'a behavior) -> ('a -> 'b) -> (exn -> 'b) -> 'b behavior
(**
- [try_bind_lift b ?eq f g] is equivalent to [try_bind b (fun v ->
+ [try_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.
*)
@@ -150,20 +185,21 @@ val fix_b : ?eq:('a -> 'a -> bool) -> ('a behavior -> 'a behavior) -> 'a behavio
val notify_b : ?current:bool -> 'a behavior -> ('a -> unit) -> unit
(**
- Adds a listener for the value of a behavior, which is called
- whenever the value changes. When the behavior fails the listener
- is not called. The notification is implicitly cancelled when the
- calling context is re-run.
-
+ [notify_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 [current] is false.
+ behavior, unless [current] is false. The function [f] delimits a
+ dynamic scope governed by [b].
*)
val notify_b_cancel : ?current:bool -> 'a behavior -> ('a -> unit) -> cancel
(**
Same as [notify_b], and returns a cancel handle (the notification
- is still implicitly cancelled).
+ is still cancelled when the enclosing dynamic scope is cleaned up).
*)
val notify_result_b : ?current:bool -> 'a behavior -> ('a result -> unit) -> unit
@@ -174,74 +210,45 @@ val notify_result_b : ?current:bool -> 'a behavior -> ('a result -> unit) -> uni
val notify_result_b_cancel : ?current:bool -> 'a behavior -> ('a result -> unit) -> cancel
(**
- Same as [notify_b_cancel], and returns a cancel handle (the notification
- is still implicitly cancelled).
+ Same as [notify_result_b], and returns a cancel handle (the
+ notification is still cancelled when the enclosing dynamic scope
+ is cleaned up).
*)
-val cleanup : (unit -> unit) -> unit
- (**
- When called in the context of a binder, adds a function to be
- called when the binder must be re-executed. You can use this to
- clean up external resources.
-
- Binds and notifies in the context of a binder are cleaned up
- automatically.
- *)
-
-val memo :
- ?size:int -> ?hash:('a -> int) -> ?eq:('a -> 'a -> bool) -> unit ->
- ('a -> 'b) ->
- ('a -> 'b)
+val hash_behavior : 'a behavior -> int
(**
- [memo f] creates a {e memo function} from [f]. Calls to the memo
- function are memoized and may be reused when the calling context
- is re-executed.
-
- [memo] does not provide general-purpose memoization; calls may be
- reused only within the calling context in which they originally
- occurred, and only in the original order they occurred.
-
- To memoize a recursive function, use the following idiom: {[
- let m = memo () in
- let rec f x = ... memo f y in
- let f x = memo f x
- ]}
-
- The default hash function is not appropriate for behaviors and
- events (since they contain mutable data) so you should use
- [hash_behavior] and [hash_event] instead.
+ A hash function for behaviors. [Hashtbl.hash] is not appropriate
+ because behaviors contain mutable data.
*)
-val hash_behavior : 'a behavior -> int
- (** A hash function for behaviors, *)
-
-
(** {2 Events} *)
type +'a event
- (** Type of events of type ['a]. *)
+ (** Type of events taking values of type ['a]. *)
type -'a event_sender
- (** Type of event senders of type ['a]. *)
+ (** Type of event senders sending values of type ['a]. *)
val make_event : unit -> 'a event * 'a event_sender
- (** Makes a new channel for events of type ['a]. *)
+ (** Makes a new event taking values of type ['a]. *)
val never : 'a event
(** An event which never occurs. *)
val notify_e : 'a event -> ('a -> unit) -> unit
(**
- Adds a listener on the channel, which is called whenever a value
- is sent on it. When a failure is sent the listener is not
- called. The notification is implicitly cancelled when the calling
- context is re-run.
+ [notify_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) -> cancel
(**
Same as [notify_e], and returns a cancel handle (the notification
- is still implicitly cancelled).
+ is still cancelled when the enclosing dynamic scope is cleaned up).
*)
val notify_result_e : 'a event -> ('a result -> unit) -> unit
@@ -252,73 +259,125 @@ val notify_result_e : 'a event -> ('a result -> unit) -> unit
val notify_result_e_cancel : 'a event -> ('a result -> unit) -> cancel
(**
- Same as [notify_e_cancel], and returns a cancel handle (the notification
- is still implicitly cancelled).
+ Same as [notify_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 -> unit
- (** [send e v] calls the listeners of the associated event with [Value v]. *)
+ (**
+ [send s v] sends the value [v] to the associated event [e], so
+ [e] occurs with value [v].
+ *)
val send_exn : 'a event_sender -> exn -> unit
- (** [send_exn e x] calls the listeners of the associated event with [Fail x]. *)
+ (**
+ [send_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 -> unit
- (** [send_result e r] calls the listeners of the associated event with [r]. *)
+ (**
+ [send_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 -> unit
- (** [send e v] calls the listeners of the associated event with [Value v] in the next update cycle. *)
+ (**
+ [send_deferred s v] enqueues a call to [send s v] for a future
+ update cycle.
+ *)
val send_exn_deferred : 'a event_sender -> exn -> unit
- (** [send_exn e x] calls the listeners of the associated event with [Fail x] in the next update cycle. *)
+ (**
+ [send_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 -> unit
- (** [send_result e r] calls the listeners of the associated event with [r] in the next update cycle. *)
+ (**
+ [send_result_deferred s r] enqueues a call to [send_result s r]
+ for a future update cycle.
+ *)
val next : 'a event -> 'a event
- (** [next e] fires just the next occurence of [e]. *)
+ (**
+ [next e] passes on only the next occurence of [e]; subsequent
+ occurrences are dropped.
+ *)
val merge : 'a event list -> 'a event
- (** [merge es] is an event that fires whenever any of the events in [e] fire. *)
+ (**
+ [merge 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 event
- (** [map f e] is an event that fires [f v] whenever [e] fires [v]. *)
+ (**
+ [map f e] is an event that fires [f v] whenever [e] fires
+ [v]. The function [f] delimits a dynamic scope governed by [e].
+ *)
val filter : ('a -> bool) -> 'a event -> 'a event
- (** [filter p e] is an event that fires [v] whenever [e] fires [v] and [p v] is true. *)
+ (**
+ [filter 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 : ('b -> 'a -> 'b) -> 'b -> 'a event -> 'b event
(**
[collect 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.
+ 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 event
- (** [join_e ee] fires whenever the event last fired from [ee] fires *)
+ (**
+ [join_e ee] occurs whenever the event which last occurred on [ee]
+ occurs.
+ *)
val fix_e : ('a event -> 'a event) -> 'a event
(**
- [fix_e ef] returns [ef e'] where [e'] is an event that fires
- whenever [ef e'] fires, but in the next update cycle.
+ [fix_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 -> int
- (** A hash function for events. *)
+ (**
+ A hash function for events. [Hashtbl.hash] is not appropriate
+ because events contain mutable data.
+ *)
-(** {2 Combinations of behaviors and events} *)
+(** {2 Combinations} *)
val switch : ?eq:('a -> 'a -> bool) -> 'a behavior -> 'a behavior event -> 'a behavior
- (** [switch b e] behaves as [b] until [e] fires, then behaves as the last value of [e]. *)
+ (**
+ [switch 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 behavior
- (** [until b e] behaves as [b] until [e] fires [b'], then behaves as [b'] *)
+ (**
+ [until 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 behavior
(**
- [hold v e] behaves as the last value fired by [e], or [v] if [e]
- has not yet fired a value (since [hold] was called). [eq]
- gives the equality on the resulting behavior.
+ [hold 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 behavior
@@ -327,14 +386,16 @@ val hold_result : ?eq:('a -> 'a -> bool) -> 'a result -> 'a event -> 'a behavior
*)
val changes : 'a behavior -> 'a event
- (** [changes b] fires the value of [b] whenever it changes. *)
+ (**
+ [changes b] occurs with the value of [b] whenever [b] changes.
+ *)
val when_true : bool behavior -> unit event
(** [when_true b] fires whenever [b] becomes true. *)
val count : 'a event -> int behavior
(**
- [count e] behaves as the number of times [e] has fired (since
+ [count e] takes on the number of times [e] has occurred (since
[count] was called).
*)
@@ -342,15 +403,84 @@ 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
- respects the update cycle (it enqueues an event) so may be used
+ enqueues the update for a future update cycle, so it may be used
freely.
*)
-(** {2 Variations} *)
+(** {2 Other} *)
-val bindN : ?eq:('b -> 'b -> bool) -> 'a behavior list -> ('a list -> 'b behavior) -> 'b behavior
-val bliftN : ?eq:('b -> 'b -> bool) -> 'a behavior list -> ('a list -> 'b) -> 'b behavior
-val liftN : ?eq:('b -> 'b -> bool) -> ('a list -> 'b) -> 'a behavior list -> 'b behavior
+val init : unit -> unit
+ (** Initialize the library; can be called again to reinitialize. *)
+
+val no_cancel : cancel
+ (** Dummy cancel. *)
+
+val cancel : cancel -> unit
+ (** Cancels a listener registration using the given handle. *)
+
+val cleanup : (unit -> unit) -> unit
+ (**
+ [cleanup 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 -> 'b)
+ (**
+ [memo 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 {e 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 = ... memo f y in
+ let f x = memo 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.
+ *)
+
+(** {2 Debugging} *)
+
+val set_exn_handler : (exn -> unit) -> unit
+ (**
+ Set an exception handler which is called on exceptions from
+ listeners.
+ *)
+
+val set_debug : (string -> unit) -> unit
+ (** Set a function for showing library debugging. *)
+
+(** {2 Variations} *)
val bind2 :
?eq:('b -> 'b -> bool) ->
@@ -448,13 +578,6 @@ val lift7 :
'a1 behavior -> 'a2 behavior -> 'a3 behavior -> 'a4 behavior -> 'a5 behavior -> 'a6 behavior -> 'a7 behavior ->
'b behavior
-(** {2 Debugging} *)
-
-val set_exn_handler : (exn -> unit) -> unit
- (**
- Set an exception handler which is called on exceptions from
- notification functions.
- *)
-
-val set_debug : (string -> unit) -> unit
- (** Set a function for showing library debugging. *)
+val bindN : ?eq:('b -> 'b -> bool) -> 'a behavior list -> ('a list -> 'b behavior) -> 'b behavior
+val bliftN : ?eq:('b -> 'b -> bool) -> 'a behavior list -> ('a list -> 'b) -> 'b behavior
+val liftN : ?eq:('b -> 'b -> bool) -> ('a list -> 'b) -> 'a behavior list -> 'b behavior
View
82 src/froc/froc_sa.mli
@@ -26,19 +26,30 @@
Acar et al. Changeable values are presented as a monad, using ideas
from [Lwt].
- A {e changeable} is a monadic value that can change over
- time. Binding a changeable causes the binder to be made a
- dependency of the changeable, and to be re-executed when the
- changeable changes. A {e writeable} associated with a changeable is
- used to change a changeable.
+ A {e changeable} is a value that can change over time. New
+ changeables may be created from existing ones using [bind] or one
+ of its variants. When a changeable changes, the changeables which
+ depend on it are updated by running their update functions
+ (i.e. the function passed to [bind]). The dependencies of a
+ changeable are updated before the changeable is updated, so that
+ the update function sees a consistent view of the dependencies.
Self-adjusting computation proceeds in phases: after an initial
computation, call [write] to change some inputs, then [propagate]
to make the result consistent again.
+
+ Most functions returning changeables take an optional [eq]
+ argument, which gives an equality function on the value of the
+ resulting changeable. A changeable is considered to have changed
+ only when 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 changeables of the same type always
+ be given the same equality.
*)
val init : unit -> unit
- (** Initialize the library. Must be called before any other function. *)
+ (** Initialize the library; can be called again to reinitialize. *)
(** {2 Changeables} *)
@@ -50,12 +61,6 @@ type -'a u
val changeable : ?eq:('a -> 'a -> bool) -> 'a -> 'a t * 'a u
(**
[changeable v] is a changeable with initial value [v].
-
- The optional [eq] argument gives an equality function; a
- changeable is considered changed (and its dependencies notified)
- only if its new value is not [eq] to its old one. The default
- equality holds iff the values [compare] to [0] (incomparable
- values are always not equal).
*)
val return : 'a -> 'a t
@@ -72,9 +77,6 @@ val bind : ?eq:('b -> 'b -> bool) -> 'a t -> ('a -> 'b t) -> 'b t
(**
[bind c f] behaves as [f] applied to the value of [c]. If [c]
fails, [bind c f] also fails, with the same exception.
-
- When the value of a changeable changes, all functions [f] bound to
- it are re-executed.
*)
val (>>=) : 'a t -> ('a -> 'b t) -> 'b t
@@ -97,26 +99,27 @@ val lift : ?eq:('b -> 'b -> bool) -> ('a -> 'b) -> 'a t -> 'b t
val catch : ?eq:('a -> 'a -> bool) -> (unit -> 'a t) -> (exn -> 'a t) -> 'a t
(**
- [catch c f] behaves the same as [c()] if [c()] succeeds. If [c()]
- fails with some exception [e], [catch c f] behaves as [f e].
+ [catch cf f] behaves the same as [cf()] if [cf()] succeeds. If
+ [cf()] fails with some exception [e], [catch cf f] behaves as [f
+ e].
*)
val catch_lift : ?eq:('a -> 'a -> bool) -> (unit -> 'a t) -> (exn -> 'a) -> 'a t
(**
- [catch_lift c ?eq f] is equivalent to [catch c (fun e -> return
+ [catch_lift cf ?eq f] is equivalent to [catch cf (fun e -> return
?eq (f e))], but is slightly more efficient.
*)
val try_bind : ?eq:('b -> 'b -> bool) -> (unit -> 'a t) -> ('a -> 'b t) -> (exn -> 'b t) -> 'b t
(**
- [try_bind c f g] behaves as [bind (c()) f] if [c()] succeeds. If
- [c()] fails with exception [e], [try_bind c f g] behaves as [g
+ [try_bind cf f g] behaves as [bind (cf()) f] if [cf()] succeeds. If
+ [cf()] fails with exception [e], [try_bind cf f g] behaves as [g
e].
*)
val try_bind_lift : ?eq:('b -> 'b -> bool) -> (unit -> 'a t) -> ('a -> 'b) -> (exn -> 'b) -> 'b t
(**
- [try_bind_lift c ?eq f g] is equivalent to [try_bind c (fun v ->
+ [try_bind_lift cf ?eq f g] is equivalent to [try_bind cf (fun v ->
return ?eq (f v)) (fun e -> return ?eq (g e))], but is slightly
more efficient.
*)
@@ -124,9 +127,6 @@ val try_bind_lift : ?eq:('b -> 'b -> bool) -> (unit -> 'a t) -> ('a -> 'b) -> (e
val read : 'a t -> 'a
(**
[read c] returns the value of [c], or raises an exception if [c] fails.
-
- You shouldn't call [read] in the context of a binder, since you
- might get a stale result.
*)
val write : 'a u -> 'a -> unit
@@ -151,22 +151,36 @@ val memo :
('a -> 'b) ->
('a -> 'b)
(**
- [memo f] creates a {e memo function} from [f]. Calls to the memo
- function are memoized and may be reused when the calling context
- is re-executed.
-
- [memo] does not provide general-purpose memoization; calls may be
- reused only within the calling context in which they originally
- occurred, and only in the original order they occurred.
- To memoize a recursive function, use the following idiom: {[
- let m = memo () in
+ [memo 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] 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 main point of [memo] is to avoid needless recomputation in
+ cases where a computation is executed in an update function for
+ some changeable, but does not actually use the changeables's
+ value. For example, in {[
+ let g = memo () fun x -> ... in
+ c >>= fun _ -> g 7
+ ]} the returned changeable is indifferent to the value of
+ [c]. Without [memo] it would be recomputed every time [c]
+ changes; with [memo] it is computed only the first time.
+
+ 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 = ... memo f y in
let f x = memo f x
]}
The default hash function is not appropriate for changeables
- (since they contain mutable data) so you should use [hash]
+ (since they contain mutable data); [hash] should be used
instead.
*)
Please sign in to comment.
Something went wrong with that request. Please try again.