Lately I have been playing around with F#, and I must admit I like the language a lot ! I think it is very expressive and very intuitive to use.
I personally think that the best way to learn a new language is to find a project that you are familiar with (you have implemented in the language you are familiar with) and then try to convert it to the new language.
So I decided to try and convert the RX MVVM framework that I showed in the first blog on this series and convert it to F#.
The first interface IPropertySubject is declared as :
type IPropertySubject<'t> =
inherit IObservable<'t>
abstract member Value : 't with get, set
inherit IObservable<'t>
abstract member Value : 't with get, set
Then the implementation looks like:
type PropertySubject<'t>() =
let _subject : Subject<'t> = new Subject<'t>()
let mutable _value : 't = Unchecked.defaultof<'t>
let setValue v =
_value <- v
_subject.OnNext(v)
member x.SetValues(obs : IObservable<'t>) =
obs.Subscribe(fun v -> setValue v)
member x.SetValue(value : 't) = setValue value
interface IPropertySubject<'t> with
member x.Value with get() = _value and set v = setValue v
member x.Subscribe observer =
_subject.Subscribe observer
let _subject : Subject<'t> = new Subject<'t>()
let mutable _value : 't = Unchecked.defaultof<'t>
let setValue v =
_value <- v
_subject.OnNext(v)
member x.SetValues(obs : IObservable<'t>) =
obs.Subscribe(fun v -> setValue v)
member x.SetValue(value : 't) = setValue value
interface IPropertySubject<'t> with
member x.Value with get() = _value and set v = setValue v
member x.Subscribe observer =
_subject.Subscribe observer
The next interface is ICommandObserver. the interface is declared as follows:
type ICommandObserver<'t> =
inherit ICommand
abstract member Execute : IObservable<'t> with get
abstract member CanExecute : IObserver<bool> with get
inherit ICommand
abstract member Execute : IObservable<'t> with get
abstract member CanExecute : IObserver<bool> with get
And the implementation :
type CommandObserver<'t>(value) as self =
let event = Event<_, _>()
let _executeSubject = new Subject<'t>()
let _canExecuteSubject = new Subject<bool>()
let mutable _canExecute = value
let disp = _canExecuteSubject.DistinctUntilChanged().Subscribe(fun v -> _canExecute <- v
event.Trigger(self, EventArgs.Empty))
new() = new CommandObserver<'t>(true)
member x.SubscribeOnValues (values : IObservable<bool>) =
values.Subscribe(_canExecuteSubject)
interface ICommandObserver<'t> with
member x.Execute with get() = _executeSubject.AsObservable()
member x.CanExecute with get() = _canExecuteSubject.AsObserver()
interface ICommand with
member x.add_CanExecuteChanged(e) = event.Publish.AddHandler(e)
member x.remove_CanExecuteChanged(e) = event.Publish.RemoveHandler(e)
member x.Execute parameter =
match parameter with | :? 't -> _executeSubject.OnNext(parameter :?> 't)
| _ -> _executeSubject.OnNext(Unchecked.defaultof<'t>)
member x.CanExecute parameter =
_canExecute
The next step is to implement ViewModelBase. The implementation in F# looks like:
type ViewModelBase() =
let event = Event<_, _>()
member x.OnPropertyChanged(propName : string) =
event.Trigger(x, new PropertyChangedEventArgs(propName))
interface INotifyPropertyChanged with
member x.add_PropertyChanged(e) = event.Publish.AddHandler(e)
member x.remove_PropertyChanged(e) = event.Publish.RemoveHandler(e)
let event = Event<_, _>()
member x.OnPropertyChanged(propName : string) =
event.Trigger(x, new PropertyChangedEventArgs(propName))
interface INotifyPropertyChanged with
member x.add_PropertyChanged(e) = event.Publish.AddHandler(e)
member x.remove_PropertyChanged(e) = event.Publish.RemoveHandler(e)
One we have ViewModelBase, then it is time to declare IPropertyProvider interface. The declaration for the interface looks like:
type IPropertyProvider<'viewmodel> = inherit IDisposable abstract member CreateProperty<'ttype> : Expression<System.Func<'viewmodel,'ttype>> -> IPropertySubject<'ttype> abstract member CreateProperty<'ttype> : Expression<System.Func<'viewmodel,'ttype>> * 'ttype -> IPropertySubject<'ttype> abstract member CreateProperty<'ttype> : Expression<System.Func<'viewmodel,'ttype>> * IObservable<'ttype> -> IPropertySubject<'ttype> abstract member CreateCommand<'ttype> : Expression<System.Func<'viewmodel, ICommand>> -> ICommandObserver<'ttype> abstract member CreateCommand<'ttype> : Expression<System.Func<'viewmodel, ICommand>> * bool -> ICommandObserver<'ttype> abstract member CreateCommand<'ttype> : Expression<System.Func<'viewmodel, ICommand>> * IObservable<bool> -> ICommandObserver<'ttype>
And the implementation is :
type PropertyProvider<'viewmodel>(viewModelBase : ViewModelBase, schedulers : ISchedulers) = let _viewModelBase = viewModelBase let _disposables = new CompositeDisposable() let getProperty (expr : Expression<System.Func<'viewmodel,'ttype>>) = let propName = (expr.Body :?> MemberExpression).Member.Name let propSubject = new PropertySubject<'t>() propSubject.SubscribeOn(schedulers.Dispatcher).Subscribe(fun _ -> _viewModelBase.OnPropertyChanged(propName)) |> _disposables.Add propSubject interface IPropertyProvider<'viewmodel> with member x.CreateProperty<'ttype> expr = let propSubject = getProperty expr propSubject :> IPropertySubject<'ttype> member x.CreateProperty<'ttype> (expr : Expression<System.Func<'viewmodel,'ttype>>, value : IObservable<'ttype>) = let propSubject = getProperty expr propSubject.SetValues(value) |> _disposables.Add propSubject :> IPropertySubject<'ttype> member x.CreateProperty<'ttype> (expr : Expression<System.Func<'viewmodel,'ttype>>, value : 'ttype) = let propSubject = getProperty expr propSubject.SetValue(value) propSubject :> IPropertySubject<'ttype> member x.CreateCommand<'ttype> expr = new CommandObserver<'ttype>(true) :> ICommandObserver<'ttype> member x.CreateCommand<'ttype>(expr : Expression<System.Func<'viewmodel, ICommand>>, value : bool) = new CommandObserver<'ttype>(value) :> ICommandObserver<'ttype> member x.CreateCommand<'ttype>(expr : Expression<System.Func<'viewmodel, ICommand>>, values : IObservable<bool>) = let cmdObserver = new CommandObserver<'ttype>() cmdObserver.SubscribeOnValues(values) |> _disposables.Add cmdObserver :> ICommandObserver<'ttype> interface IDisposable with member x.Dispose() = _disposables.Dispose()
The next step is to declare a factory that will create property provider. The interface for the factory looks like :
type IPropertyProviderFactory = abstract member Create<'t> : ViewModelBase -> IPropertyProvider<'t>
The implementation of the interface looks like :
type PropertyProviderFactory(schedulers : ISchedulers) = interface IPropertyProviderFactory with member x.Create<'t> (vm : ViewModelBase) = new PropertyProvider<'t>(vm, schedulers) :> IPropertyProvider<'t>
Next we need to implement message bus. The interface for the message bus is as follows :
type IMessageBus = abstract member Subscribe<'t> : System.Action<'t> -> IDisposable abstract member Publish<'t> : 't -> unit
And the implementation is :
type MessageBus() = let _messageBus = new Subject<Object>() interface IMessageBus with member x.Subscribe<'t> onNext = _messageBus.OfType<'t>().Subscribe(onNext) member x.Publish<'t>(value : 't) = _messageBus.OnNext(value :> Object)
And lastly we will declare and implement schedulers. The interface for the schedulers looks like :
type ISchedulers = abstract member Dispatcher : IScheduler with get abstract member NewThread : IScheduler with get abstract member NewTask : IScheduler with get abstract member ThreadPool : IScheduler with get abstract member Timer : IScheduler with get
And the implementation is as follows:
type Schedulers(guiSynchronizationContext : IGuiSynchronizationContext) = let _dispatcher = new SynchronizationContextScheduler(guiSynchronizationContext.SynchronizationContext) interface ISchedulers with member x.Dispatcher with get() = _dispatcher :> IScheduler member x.NewThread with get() = Scheduler.NewThread :> IScheduler member x.NewTask with get() = Scheduler.TaskPool :> IScheduler member x.ThreadPool with get() = Scheduler.ThreadPool :> IScheduler member x.Timer with get() = Scheduler.Immediate :> IScheduler
As you can see from the code above, F# code is a lot more compact and has a lot less boilerplate and ceremony.
I hope you find it interesting and helpful.