Jeremy Abbott (Not the Figure Skater)

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

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