Phillip Trelford's Array

POKE 36879,255

What’s happening? Loosely coupled events

So the Reactive Extensions (Rx) demonstrate some sweet syntax for handling CLR events. How about doing the same thing for loosely coupled events exposed through an Event Broker like Prism’s IEventAggregator. For example if you wanted to publish an event from any window that could be observed by any other window without them all having intimate knowledge of each other.

The following is a simple F# event broker coded on the train this morning (Note: the interface ISubscribe<’a> could be replaced by IObservable<’a>):

type ITrigger<'a> =
    abstract member Trigger : 'a -> unit
    
type ISubscribe<'a> =     
    abstract member Subscribe : ('a -> unit) -> unit 

type IHappen<'a> = 
    inherit ITrigger<'a> 
    inherit ISubscribe<'a>
            
type IHappenings =
    abstract member ObtainHappening<'a> : unit -> IHappen<'a>
        
type Happenings () =
    let happenings = System.Collections.Generic.Dictionary<System.Type,_>()
    interface IHappenings with
        member this.ObtainHappening<'a> () =
            let CreateHappening () = 
                let subscribers = ref []
                { new IHappen<'a> with                        
                    member this.Subscribe f = 
                        lock(this) (fun _ -> 
                            subscribers := f::!subscribers)
                    member this.Trigger (x) =                      
                        !subscribers |> List.iter (fun f -> f x)                       
                }
            lock (this) (fun _ ->    
                match happenings.TryGetValue(typeof<'a>) with
                | true, happen -> unbox happen
                | false, _ ->                 
                    let happen = CreateHappening ()
                    happenings.Add(typeof<'a>,box happen)
                    happen
            )                    
    end

 

Then, in Starbucks for a coffee and some higher-order functions :

module Happening =   
    open System
    open System.Windows.Threading

    let Subscribe<'a> f (happening:ISubscribe<'a>) =         
        happening.Subscribe f
    let OnDispatcher<'a> (happening:ISubscribe<'a>) =
        let dispatcher = Dispatcher.CurrentDispatcher
        { new ISubscribe<'a> with  
            member this.Subscribe f = 
                happening.Subscribe (fun x -> 
                dispatcher.Invoke(Action(fun _ -> f x)) |> ignore
            )            
        }        
    let Filter<'a> filterF (happening:ISubscribe<'a>) =
        { new ISubscribe<'a> with  
            member this.Subscribe f = 
                happening.Subscribe (fun x -> if filterF x then f x)          
        }
    let Map<'a,'b> mapF (happening:ISubscribe<'a>) =        
        { new ISubscribe<'b> with  
            member this.Subscribe f = 
                happening.Subscribe (fun x -> let y = mapF x in f y)            
        }
    let Delay<'a> milliseconds (happening:ISubscribe<'a>) =
        { new ISubscribe<'a> with  
            member this.Subscribe f = 
                happening.Subscribe (fun x -> 
                    async { 
                        do! Async.Sleep(milliseconds) 
                        f x 
                    } |> Async.Start )            
        }

 

Finally, here is Matthew Podwysocki’s cool Time Flies Like An Arrow sample rewritten to use more loosely-coupled events:

module Test =  
    open System
    open System.Windows
    open System.Windows.Controls
    open System.Windows.Input
    open System.Windows.Media
 
    let getPosition (element : #UIElement) (args : MouseEventArgs) =
        let point = args.GetPosition(element)
        (point.X, point.Y)    
     
    type TimeFliesWindow(happenings:IHappenings) as this =
        inherit Window()

        do this.Title <- "Time files like an arrow"

        let canvas = 
           Canvas(Width=800.0, Height=400.0, Background = Brushes.White) 
        do this.Content <- canvas

        let happen = happenings.ObtainHappening<MouseEventArgs>()
        do this.MouseMove         
            |> Observable.subscribe happen.Trigger |> ignore

        do "F# can react to first class events!"
            |> Seq.iteri(fun i c ->  
                let s = TextBlock(Width=20.0, 
                                Height=30.0, 
                                FontSize=20.0, 
                                Text=string c, 
                                Foreground=Brushes.Black, 
                                Background=Brushes.White)
                canvas.Children.Add(s) |> ignore              
                happen                  
                |> Happening.Map (getPosition canvas)           
                |> Happening.Delay (i * 100)
                |> Happening.OnDispatcher                            
                |> Happening.Subscribe (fun (x, y) ->                                                        
                     Canvas.SetTop(s, y) 
                     Canvas.SetLeft(s, x + float ( i * 10)))              
           )        
              
    let happenings = new Happenings()
    let win = TimeFliesWindow(happenings)
    [<STAThread>]
    do (new Application()).Run(win) |> ignore

Pingbacks and trackbacks (5)+

Comments are closed