F# Events, Reactive Programming and Async Workflows
This is my contribution to the 2015 F# Advent Calendar. I hope you like it!
The majority of my code-slinging career has focused on full stack web development which hasn't necessitated a lot of complex event-driven programming, unless you include the unholy event model in Web Forms. Even in the cases where I do need to work with events, it's usually abstracted behind a library or framework (shout-out to Knockout). Working with events has become much more relevant now that I'm doing more mobile development. Fortunately F# has some awesome language features that make working with events a lot of fun. Before we jump into events though we're going to look briefly at async programming in F#. Then we'll look at the F# programming model for events. Finally, we'll bring it all together in a comprehensive, if a little contrived, example.
F# Async Workflows
For whatever reason, F# async workflows have been one of the hardest things for me to grok. The concepts started to click when I read this excellent write-up on the subject.
The goal of asynchronous programming is to start expensive and/or time consuming processes on a separate thread and continue doing work on the main thread until we have to wait for the expensive task to finish. It's kind of like multitasking except that asynchronous programming actually is more efficient. ;)
The following code sample shows a couple of expensive tasks running in sequence. We can't do anything on the main thread until they complete.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
|
open System
let timer1 = new Timers.Timer(500.)
let timer2 = new Timers.Timer(250.)
timer1.Elapsed.Add
(fun x ->
printfn "Timer 1: %d:%d" x.SignalTime.Second x.SignalTime.Millisecond)
timer2.Elapsed.Add
(fun x ->
printfn "Timer 2: %d:%d" x.SignalTime.Second x.SignalTime.Millisecond)
let runTimer (t : System.Timers.Timer) (s : int) =
t.Start()
System.Threading.Thread.Sleep s
t.Stop()
//Run timers sequentially
runTimer timer1 2500
runTimer timer2 2500
|
While the above code is contrived, it's not hard to imagine each timer representing an HTTP request to a web API to get data that our app needs before it can be useful. As it stands we have to wait 5 seconds for these tasks to finish before the app startup completes.
Now here's the same code using F# async workflows:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
|
open System
let timer1 = new Timers.Timer(500.)
let timer2 = new Timers.Timer(250.)
timer1.Elapsed.Add
(fun x ->
printfn "Timer 1: %d:%d" x.SignalTime.Second x.SignalTime.Millisecond)
timer2.Elapsed.Add
(fun x ->
printfn "Timer 2: %d:%d" x.SignalTime.Second x.SignalTime.Millisecond)
// new stuff starts here
let runTimerAsync (t : System.Timers.Timer) s =
async {
t.Start()
// Async workflow needs to do something (basically block here)
do! Async.Sleep s
t.Stop()
}
// Run timers in parallel
Async.Parallel [(runTimerAsync timer1 5000); (runTimerAsync timer2 5000)]
|> Async.RunSynchronously
|
Thanks to async workflows, we've cut the amount of time it takes to regain control of the main thread nearly in half. But what if we could fire off each request independently and let the async workflow notify us when it's done? That's exactly what the following example does:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
|
Async.StartWithContinuations(
runTimerAsync timer1 2500,
(fun _ -> printfn "Timer 1 finished"),
(fun _ -> printfn "Timer 1 threw an exception"),
(fun _ -> printfn "Cancelled Timer 1"))
Async.StartWithContinuations(
runTimerAsync timer2 1500,
(fun _ -> printfn "Timer 2 finished"),
(fun _ -> printfn "Timer 2 threw an exception"),
(fun _ -> printfn "Cancelled Timer 2"))
printfn "Some other task that isn't blocked"
// Output:
// "Some other task that isn't blocked"
// timer output...
// "Timer 2 finished"
// "Timer 1 finished"
|
In the example above, the list of async workflows is broken into two Async.StartWithContinuations calls. Each runs independently somewhere in the background and is given a function to execute when the task is done. Each async call is non-blocking, which means work can continue on the main thread until the expensive task completes.
F# Events
F# events are straightforward to work with. When working with existing events, you can add a handler to the event using the Event.Add
method. Referring back to the timer instances from the previous example, we were able to add handlers for the Elapsed
event with lambdas (anonymous functions) like this: timer.Elapsed.Add (fun x -> printfn "%A" x)
.
Adding a custom event to a type (class) is also simple. Let's create a type that determines whether or not a number is prime and fires an event when called. I chose prime numbers because they come up a lot in programming exercises, and it's interesting see the different ways to determine if a number is prime efficiently.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
|
// Type for testing primality of numbers
// Include custom event that triggers when helper is called.
type PrimeFinder() =
let primeFoundEvent = Event<_>()
member x.PrimeFound = primeFoundEvent.Publish
member x.IsPrime n =
primeFoundEvent.Trigger(x, (n, isPrime n))
let primeFinder = PrimeFinder()
primeFinder.PrimeFound.Add (fun (_, e) ->
match e with
| n, true -> printfn "%d is prime" n
| n, false -> printfn "%d is not prime" n)
primeFinder.IsPrime 3L // prints "3 is prime"
// PrimeFound event should not fire
primeFinder.IsPrime 4L // prints "4 is not prime"
|
PrimeFinder
is a type that exposes a single method, IsPrime
, and an event, PrimeFound
, that we can add handlers to. primeFoundEvent
is a local value of type Event<_>
. Notice that the type passed to Event
is not defined explicitly. By using the wildcard "_
" syntax we're telling the compiler to figure out the event type based on its usage. PrimeFinder
exposes primeFoundEvent
as PrimeFound
via the Publish
method, which returns an object of type IEvent<PrimeFinder * obj>
. Finally, IsPrime
calls the Trigger
method on primeFoundEvent
. The arguments passed to Trigger
are the self-identifier for the type, x
, and a tuple of type int64 * bool
. Said another way, when we call the Trigger
method we're passing it the sender and eventArgs.
Note that when IsPrime
calls primeFoundEvent.Trigger
with arguments of type int64 * bool
, the F# compiler infers PrimeFound
's type to be IEvent<PrimeFinder *(int64 * bool)>
.
One of the neat things about F# events is that you can treat them as event streams (like with Rx programming). An event stream is essentially a collection of events that you can apply function calls like map
, reduce
, and filter
to.
So maybe we only want the event to call our handler when a number is prime. We could easily update our handler to print only when a number is prime, but what if we could separate out the responsibility of handling the event from determining whether or not we should handle the event?
The following sample creates an event stream from PrimeFound
and returns only events whose arguments are prime. The previous handler is then added to the filtered stream. The logic for determining when to handle an event is now separate from the logic describing how to handle an event.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
let primeFinder = PrimeFinder()
primeFinder.PrimeFound
|> Event.filter (fun (_, e) -> snd e) // only where the number was prime
|> Event.add (fun (_, e) ->
match e with
| n, true -> printfn "%d is prime" n
| n, false -> printfn "%d is not prime" n)
// PrimeFound handler should fire.
primeFinder.IsPrime 3L
// PrimeFound handler should not fire
primeFinder.IsPrime 4L
|
What's really nice about this approach is that we can change our filtering logic at any time without touching the event handler. PrimeFound
can be treated as a stream by virtue of being of type IEvent thanks to primeFoundEvent.Publish
.
More Events
Now let's say we want to start a long running process in the background and we want to be able to check on the status of it as we go. The code for the background process might look something like:
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
let findPrimesAsync numbers primeTester =
let rec findPrimes n = async {
match n with
| h::t ->
do! Async.SwitchToNewThread()
primeTester h
do! Async.SwitchToThreadPool()
do! findPrimes t
| [] -> ()
}
findPrimes number
|
findPrimesAsync
takes a list of numbers and a function that determines the primality of a number and then applies that function to each number in an async workflow. There are a lot of ways to iterate over the collection and apply the primeTester
function to each item, but I went with an F# list to see what it looked like being solved recursively. Note that the first time you call the inner recurisve function findPrimes
you can't use the do!
binding because you're outside the async workflow. (This is probably obvious to most people but it wasn't to me. Fortunately the compiler will tell you.)
At this point we could declare a mutable binding (boo), fire off Async.Start on findPrimesAsync
and use PrimeFinder
to set the mutable binding whenever a prime is found. We could then access the mutable binding later. However, the best way to get comfortable with F# is to write it idiomatically so let's see how we can tackle this functionally, and without mutable state.
We already know we can wire a handler that will get called whenever a prime is found. It would be nice if there were an event whose argument was the most recent prime number to be processed...
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
|
// Record type give our event args some structure
type PrimeTime =
{Time:DateTime; Prime:int64 option; ID:string}
override x.ToString() =
sprintf "%d - %s"
(match x.Prime with | Some(e) -> e | None -> 0L)
(x.Time.ToLongTimeString())
let primeFinder = PrimeFinder()
let pFound =
primeFinder.PrimeFound
|> Event.filter (fun (_, e) -> snd e) // only where the number was prime
|> Event.map (fun (_, e) ->
{Time = DateTime.Now; Prime = Some(fst e); ID = "p"})
|
The value pFound
only includes events where the PrimeFound
returned true. The event argument is then mapped to the record type PrimeTime
. This record type provides extra context about the event, such as when the event fired, where the event originated from, and what the event arguments were.
For the sake of this exercise, we'll check the status of the list processing with a button click. When the button is clicked, we want to see the most recent prime number found in the collection. The following code takes the click event from a button and maps it onto an Event
of type PrimeTime
, just like pFound
!
1:
2:
3:
4:
5:
|
let checkStatusButton = new Button(Text = "Check Status")
let csClicked =
checkStatusButton.Click
|> Event.map (fun _ ->
{Time = DateTime.Now; Prime = None; ID = "c"})
|
pFound
and csClicked
are both of type IEvent<PrimeTime>
. This is ultimately the type needed for the final event stream. Getting there is a little bit interesting though.
csClicked
will never have a value for Prime
. However, pFound
will always have a value for Prime
. What we need is a stream containing the latest event emitted by both pFound
and csClicked
.
The following snippet uses Event.merge
to combine the streams pFound
and csClicked
. Event.scan
then reduces the merged stream into a stream of type IEvent<PrimeTime option * PrimeTime option>
. The first PrimeTime option
in the tuple is either Some
or None
from the pFound
stream. The second PrimeTime option
in the tuple is either Some
or None
from the csClicked
stream. Each time either pFound
or csClicked
emits an event, Event.scan
passes the tuple and newest event through an aggregation function. When a pFound
event passes through the aggregate function a tuple of (Some(e), None)
is returned. When a csClicked
event passes through the aggregate function, a tuple of (p, Some(e))
is returned. Using this pattern, the aggregate function will always return the latest PrimeFound
event (if any) when e
is a Click
event, but will not return a Click
event with when e
is PrimeFound
event. Doing this guarantees that when a tuple with a Click
event is returned, it will have the most recent prime number found if there is one.
1:
2:
3:
4:
5:
6:
7:
8:
|
let reduced =
pFound
|> Event.merge csClicked
|> Event.scan (fun (p, c) e ->
match e.ID with
|"p" -> (Some(e), None)
|"c" -> (p, Some(e))
|_ -> (p, c)) (None, None)
|
With reduced
in hand, we can now get the most recent prime number found when the "Check Status" button is clicked. This new event stream should only emit events when the "Check Status" button is clicked. This means we only want events from the reduced
stream where the second value in the tuple is Some
. After the stream is filtered to make sure that a "Check Status" click occurred, we map the event stream back into an IEvent<PrimeTime>
stream. The objective of this map operation is updating the None
value on the button click event with the Some
value from the pFound
event if there is one. If the pFound
stream hasn't emitted an event we return the unchanged csClicked
event. If a pFound
event has occurred, the prime number found from that event is swapped in for the None
emitted by the csClicked
event.
Below is the full code for the filtered, mapped, and refiltered statusCheck
stream.
1:
2:
3:
4:
5:
6:
7:
|
let statusCheck =
reduced
|> Event.filter (fun (p, c) -> c.IsSome)
|> Event.map (fun (p, c) ->
match p with
| Some f -> { c.Value with Prime = f.Prime }
| None -> c.Value)
|
With an async workflow defined for our background process and an event stream we can listen to to get the last prime found we're ready to bring it all together. Below is the full code for this example. Try running it in F# interactive. Clicking the "Check Status" button will write either the latest prime found or 0 (zero) to FSI. Clicking "Cancel" will kill the async workflow.
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
58:
59:
60:
61:
62:
63:
64:
65:
66:
67:
68:
69:
70:
71:
72:
73:
74:
75:
76:
77:
78:
79:
80:
81:
82:
83:
84:
85:
86:
87:
88:
89:
90:
91:
92:
93:
94:
95:
96:
97:
98:
99:
100:
101:
102:
103:
104:
|
open System
open System.Windows.Forms
let isPrime x =
let rec check i =
double i > sqrt (double x) || (x % i <> 0L && check (i + 1L))
match x with
| y when y < 2L -> false
| _ -> check 2L
type PrimeFinder() =
let primeFoundEvent = Event<_>()
member x.PrimeFound = primeFoundEvent.Publish
member x.IsPrime n =
primeFoundEvent.Trigger(x, (n, isPrime n))
type PrimeTime =
{Time:DateTime; Prime:int64 option; ID:string}
override x.ToString() =
sprintf "[%s] %d - %s"
x.ID
(match x.Prime with | Some(e) -> e | None -> 0L)
(x.Time.ToLongTimeString())
let findPrimesAsync numbers primeTester =
let rec findPrimes n = async {
match n with
| h::t ->
do! Async.SwitchToNewThread()
primeTester h
do! Async.SwitchToThreadPool()
do! findPrimes t
| [] -> ()
}
findPrimes numbers
let runForm () =
let panel = new FlowLayoutPanel()
panel.Dock <- DockStyle.Fill
panel.WrapContents <- false
let form = new Form(Width = 400, Height = 300, Visible = true, Text = "Test")
form.Controls.Add panel
let startButton = new Button(Text = "Start")
let cancelButton = new Button(Text = "Cancel")
let checkStatusButton = new Button(Text = "CheckStatus")
let primeFinder = PrimeFinder()
// Transform the event args
// from the button.Click
// and PrimeFound events
// into uniform structures
// that can be merged
let csClicked =
checkStatusButton.Click
|> Event.map (fun _ -> {Time = DateTime.Now; Prime = None; ID = "c"})
let pFound =
primeFinder.PrimeFound
|> Event.filter (fun (_, e) -> snd e) // only where the number was prime
|> Event.map (fun (_, e) ->
{Time = DateTime.Now; Prime = Some(fst e); ID = "p"})
let reduced =
pFound
|> Event.merge csClicked
|> Event.scan (fun (p, c) e ->
match e.ID with
|"p" -> (Some(e), None)
|"c" -> (p, Some(e))
|_ -> (p, c)) (None, None)
let statusCheck =
reduced
|> Event.filter (fun (p, c) -> c.IsSome)
|> Event.map (fun (p, c) ->
match p with
| Some f -> { c.Value with Prime = f.Prime }
| None -> c.Value)
statusCheck.Add (fun x -> printfn "Last prime: %s" <| x.ToString())
let numbers = [1L..100000L]
startButton.Click.Add (fun _ ->
printfn "Started"
Async.StartWithContinuations(
findPrimesAsync numbers primeFinder.IsPrime,
(fun _ -> printfn "Done"),
(fun x -> printfn "Exception occurred: %A" x),
(fun _ -> printfn "Stopped")
)
)
cancelButton.Click.Add (fun _ -> Async.CancelDefaultToken())
panel.Controls.AddRange [|startButton;cancelButton;checkStatusButton|]
Application.Run(form)
runForm()
|
Conclusion
The basics of F# events are simple but powerful. F# events are of type Event
and implement IEvent
which in turn implements IObservable
and IDelegateEvent
. Because of this, IEvent
can be mapped, filtered, and more, which allows consumers to add handlers for highly specialized workflows. We can start expensive tasks asynchronously and use events to check on the status of the work.
Resources
- http://fsharpforfunandprofit.com/posts/concurrency-async-and-parallel/
- http://dotnet.readthedocs.org/en/latest/async/async-fsharp.html
- http://fsharpforfunandprofit.com/posts/concurrency-reactive/
namespace System
val timer1 : Timers.Timer
Full name: fsharpeventsrxasync.timer1
namespace System.Timers
Multiple items
type Timer =
inherit Component
new : unit -> Timer + 1 overload
member AutoReset : bool with get, set
member BeginInit : unit -> unit
member Close : unit -> unit
member Enabled : bool with get, set
member EndInit : unit -> unit
member Interval : float with get, set
member Site : ISite with get, set
member Start : unit -> unit
member Stop : unit -> unit
...
Full name: System.Timers.Timer
--------------------
Timers.Timer() : unit
Timers.Timer(interval: float) : unit
val timer2 : Timers.Timer
Full name: fsharpeventsrxasync.timer2
event Timers.Timer.Elapsed: IEvent<Timers.ElapsedEventHandler,Timers.ElapsedEventArgs>
member IObservable.Add : callback:('T -> unit) -> unit
val x : Timers.ElapsedEventArgs
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
property Timers.ElapsedEventArgs.SignalTime: DateTime
property DateTime.Second: int
property DateTime.Millisecond: int
val runTimer : t:Timers.Timer -> s:int -> unit
Full name: fsharpeventsrxasync.runTimer
val t : Timers.Timer
val s : int
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
Timers.Timer.Start() : unit
namespace System.Threading
Multiple items
type Thread =
inherit CriticalFinalizerObject
new : start:ThreadStart -> Thread + 3 overloads
member Abort : unit -> unit + 1 overload
member ApartmentState : ApartmentState with get, set
member CurrentCulture : CultureInfo with get, set
member CurrentUICulture : CultureInfo with get, set
member DisableComObjectEagerCleanup : unit -> unit
member ExecutionContext : ExecutionContext
member GetApartmentState : unit -> ApartmentState
member GetCompressedStack : unit -> CompressedStack
member GetHashCode : unit -> int
...
Full name: System.Threading.Thread
--------------------
Threading.Thread(start: Threading.ThreadStart) : unit
Threading.Thread(start: Threading.ParameterizedThreadStart) : unit
Threading.Thread(start: Threading.ThreadStart, maxStackSize: int) : unit
Threading.Thread(start: Threading.ParameterizedThreadStart, maxStackSize: int) : unit
Threading.Thread.Sleep(timeout: TimeSpan) : unit
Threading.Thread.Sleep(millisecondsTimeout: int) : unit
Timers.Timer.Stop() : unit
val runTimerAsync : t:Timers.Timer -> s:int -> Async<unit>
Full name: fsharpeventsrxasync.runTimerAsync
val async : AsyncBuilder
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
Multiple items
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member Choice : computations:seq<Async<'T option>> -> Async<'T option>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
static member Async.Sleep : millisecondsDueTime:int -> Async<unit>
static member Async.Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:Threading.CancellationToken -> 'T
static member Async.StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:Threading.CancellationToken -> unit
Multiple items
type PrimeFinder =
new : unit -> PrimeFinder
member IsPrime : n:int64 -> unit
member PrimeFound : IEvent<PrimeFinder * (int64 * bool)>
Full name: fsharpeventsrxasync.PrimeFinder
--------------------
new : unit -> PrimeFinder
val primeFoundEvent : Event<PrimeFinder * (int64 * bool)>
Multiple items
module Event
from Microsoft.FSharp.Control
--------------------
type Event<'T> =
new : unit -> Event<'T>
member Trigger : arg:'T -> unit
member Publish : IEvent<'T>
Full name: Microsoft.FSharp.Control.Event<_>
--------------------
type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =
new : unit -> Event<'Delegate,'Args>
member Trigger : sender:obj * args:'Args -> unit
member Publish : IEvent<'Delegate,'Args>
Full name: Microsoft.FSharp.Control.Event<_,_>
--------------------
new : unit -> Event<'T>
--------------------
new : unit -> Event<'Delegate,'Args>
val x : PrimeFinder
member PrimeFinder.PrimeFound : IEvent<PrimeFinder * (int64 * bool)>
Full name: fsharpeventsrxasync.PrimeFinder.PrimeFound
property Event.Publish: IEvent<PrimeFinder * (int64 * bool)>
member PrimeFinder.IsPrime : n:int64 -> unit
Full name: fsharpeventsrxasync.PrimeFinder.IsPrime
val n : int64
member Event.Trigger : arg:'T -> unit
val primeFinder : PrimeFinder
Full name: fsharpeventsrxasync.primeFinder
property PrimeFinder.PrimeFound: IEvent<PrimeFinder * (int64 * bool)>
val e : int64 * bool
member PrimeFinder.IsPrime : n:int64 -> unit
val filter : predicate:('T -> bool) -> sourceEvent:IEvent<'Del,'T> -> IEvent<'T> (requires delegate and 'Del :> Delegate)
Full name: Microsoft.FSharp.Control.Event.filter
val snd : tuple:('T1 * 'T2) -> 'T2
Full name: Microsoft.FSharp.Core.Operators.snd
val add : callback:('T -> unit) -> sourceEvent:IEvent<'Del,'T> -> unit (requires delegate and 'Del :> Delegate)
Full name: Microsoft.FSharp.Control.Event.add
val findPrimesAsync : numbers:'a -> primeTester:('b -> unit) -> Async<unit>
Full name: fsharpeventsrxasync.findPrimesAsync
val numbers : 'a
val primeTester : ('a -> unit)
val findPrimes : ('a list -> Async<unit>)
val n : 'a list
val h : 'a
val t : 'a list
static member Async.SwitchToNewThread : unit -> Async<unit>
static member Async.SwitchToThreadPool : unit -> Async<unit>
type PrimeTime =
{Time: DateTime;
Prime: int64 option;
ID: string;}
override ToString : unit -> string
Full name: fsharpeventsrxasync.PrimeTime
PrimeTime.Time: DateTime
Multiple items
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
DateTime()
(+0 other overloads)
DateTime(ticks: int64) : unit
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
(+0 other overloads)
PrimeTime.Prime: int64 option
Multiple items
val int64 : value:'T -> int64 (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int64
--------------------
type int64 = Int64
Full name: Microsoft.FSharp.Core.int64
--------------------
type int64<'Measure> = int64
Full name: Microsoft.FSharp.Core.int64<_>
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
PrimeTime.ID: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
val x : PrimeTime
override PrimeTime.ToString : unit -> string
Full name: fsharpeventsrxasync.PrimeTime.ToString
val sprintf : format:Printf.StringFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
union case Option.Some: Value: 'T -> Option<'T>
val e : int64
union case Option.None: Option<'T>
DateTime.ToLongTimeString() : string
val pFound : IEvent<PrimeTime>
Full name: fsharpeventsrxasync.pFound
val map : mapping:('T -> 'U) -> sourceEvent:IEvent<'Del,'T> -> IEvent<'U> (requires delegate and 'Del :> Delegate)
Full name: Microsoft.FSharp.Control.Event.map
property DateTime.Now: DateTime
val fst : tuple:('T1 * 'T2) -> 'T1
Full name: Microsoft.FSharp.Core.Operators.fst
val checkStatusButton : obj
Full name: fsharpeventsrxasync.checkStatusButton
namespace System.Text
val csClicked : IEvent<PrimeTime>
Full name: fsharpeventsrxasync.csClicked
val reduced : IEvent<PrimeTime option * PrimeTime option>
Full name: fsharpeventsrxasync.reduced
val merge : event1:IEvent<'Del1,'T> -> event2:IEvent<'Del2,'T> -> IEvent<'T> (requires delegate and 'Del1 :> Delegate and delegate and 'Del2 :> Delegate)
Full name: Microsoft.FSharp.Control.Event.merge
val scan : collector:('U -> 'T -> 'U) -> state:'U -> sourceEvent:IEvent<'Del,'T> -> IEvent<'U> (requires delegate and 'Del :> Delegate)
Full name: Microsoft.FSharp.Control.Event.scan
val p : PrimeTime option
val c : PrimeTime option
val e : PrimeTime
val statusCheck : IEvent<PrimeTime>
Full name: fsharpeventsrxasync.statusCheck
property Option.IsSome: bool
val f : PrimeTime
property Option.Value: PrimeTime
namespace System.Windows
namespace System.Windows.Forms
val isPrime : x:int64 -> bool
Full name: fsharpeventsrxasync.isPrime
val x : int64
val check : (int64 -> bool)
val i : int64
Multiple items
val double : value:'T -> double (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.double
--------------------
type double = Double
Full name: Microsoft.FSharp.Core.double
val sqrt : value:'T -> 'U (requires member Sqrt)
Full name: Microsoft.FSharp.Core.Operators.sqrt
val y : int64
val findPrimesAsync : numbers:'a list -> primeTester:('a -> unit) -> Async<unit>
Full name: fsharpeventsrxasync.findPrimesAsync
val numbers : 'a list
val runForm : unit -> unit
Full name: fsharpeventsrxasync.runForm
val panel : FlowLayoutPanel
Multiple items
type FlowLayoutPanel =
inherit Panel
new : unit -> FlowLayoutPanel
member FlowDirection : FlowDirection with get, set
member GetFlowBreak : control:Control -> bool
member LayoutEngine : LayoutEngine
member SetFlowBreak : control:Control * value:bool -> unit
member WrapContents : bool with get, set
Full name: System.Windows.Forms.FlowLayoutPanel
--------------------
FlowLayoutPanel() : unit
property Control.Dock: DockStyle
type DockStyle =
| None = 0
| Top = 1
| Bottom = 2
| Left = 3
| Right = 4
| Fill = 5
Full name: System.Windows.Forms.DockStyle
field DockStyle.Fill = 5
property FlowLayoutPanel.WrapContents: bool
val form : Form
Multiple items
type Form =
inherit ContainerControl
new : unit -> Form
member AcceptButton : IButtonControl with get, set
member Activate : unit -> unit
member ActiveMdiChild : Form
member AddOwnedForm : ownedForm:Form -> unit
member AllowTransparency : bool with get, set
member AutoScale : bool with get, set
member AutoScaleBaseSize : Size with get, set
member AutoScroll : bool with get, set
member AutoSize : bool with get, set
...
nested type ControlCollection
Full name: System.Windows.Forms.Form
--------------------
Form() : unit
property Control.Controls: Control.ControlCollection
Control.ControlCollection.Add(value: Control) : unit
val startButton : Button
Multiple items
type Button =
inherit ButtonBase
new : unit -> Button
member AutoSizeMode : AutoSizeMode with get, set
member DialogResult : DialogResult with get, set
member NotifyDefault : value:bool -> unit
member PerformClick : unit -> unit
member ToString : unit -> string
event DoubleClick : EventHandler
event MouseDoubleClick : MouseEventHandler
Full name: System.Windows.Forms.Button
--------------------
Button() : unit
val cancelButton : Button
val checkStatusButton : Button
val primeFinder : PrimeFinder
val csClicked : IEvent<PrimeTime>
event Control.Click: IEvent<EventHandler,EventArgs>
val pFound : IEvent<PrimeTime>
val reduced : IEvent<PrimeTime option * PrimeTime option>
val statusCheck : IEvent<PrimeTime>
override PrimeTime.ToString : unit -> string
val numbers : int64 list
val x : exn
static member Async.CancelDefaultToken : unit -> unit
Control.ControlCollection.AddRange(controls: Control []) : unit
type Application =
static member AddMessageFilter : value:IMessageFilter -> unit
static member AllowQuit : bool
static member CommonAppDataPath : string
static member CommonAppDataRegistry : RegistryKey
static member CompanyName : string
static member CurrentCulture : CultureInfo with get, set
static member CurrentInputLanguage : InputLanguage with get, set
static member DoEvents : unit -> unit
static member EnableVisualStyles : unit -> unit
static member ExecutablePath : string
...
nested type MessageLoopCallback
Full name: System.Windows.Forms.Application
Application.Run() : unit
Application.Run(context: ApplicationContext) : unit
Application.Run(mainForm: Form) : unit