Posts Everything you need to know about F# classes in 23 view models
Post
Cancel

Everything you need to know about F# classes in 23 view models

The View model is the centerpiece to MVVM, with no external dependencies. As a result, the view model is an excellent class that requires no libraries while also exercising many language features related to classes. These requirements make view models a great example for learning how to build classes in a new language.

If learning F# (FSharp) is on your bucket list, getting started with classes is the easiest. Everything you need to know about F# classes is covered here. This post shows 23 (mostly) useless view models to get you started with F#. At the end, you will know how to make any useful view model in F#, so let’s get started!

View Model: #1

As already stated, in the purest sense, a view model is just a class. Creating classes in F# is really easy. Here is our first view model

1: 
2: 
type ViewModel1() = 
    member this.Foo() = 42

There is no need to create a new file. The only requirement is that it is declared before it is used. type is synonymous with the class keyword. ViewModel is also public by default, which is generally what the developer intended. Parenthesis follow the class name.

Foo is a public method. They keyword member is used to declare anything that belongs to the class. Additionally don’t forget the instance for the method this. required before the name of the method.

A few striking differences that immediately stand out to most developers is the lack of curly braces and types. The braces are omitted in favour of indentation. Should any problems arise with indentation, the compiler will highlight the line. Secondly, the lack of types can seam daunting. The F# compiler utilizes advanced mathematics so this generally not a problem. All items are given a type and checked for consistent usage. This is known as type inference.

Variables: More than one way

Variables are possible in F#, and the language even has two ways of doing it. An example of each is listed below:

1: 
2: 
3: 
4: 
5: 
type ViewModel2() = 
    let mutable _variable = 42

type ViewModel3() = 
    let _variable = ref 42

A key aspect to highlight in the examples above is that mutation/variables require ‘opt in’ with keywords. By default, all items are immutable, which is what most developers prefer. Predictable code is the result of immutability. Next up, our view model needs a getter to expose the variables.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
type ViewModel4() = 
    let mutable _variable = 42
    member this.Foo = _variable

type ViewModel5() = 
    let _variable = ref 42
    member this.Foo = !_variable

As with the methods, the member keyword is used, but the parentheses are left off. For the ref variable, note the ! that is used to return the value contained. If this is missed, the wrong type will be returned. If a setter is also present (shown later), then a compiler error will result. With the getters defined, a setter is is the next step. On a side note, in F# <- is used for assignment. For the ref there is a shorthand := to update the value.

Adding a setter to the property is simple and straight forward. Append the keyword and with set and then name the parameter to function, value is commonly used.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
type ViewModel6() = 
    let mutable _variable = 42
    member this.Foo  
        with get() = _variable
        and set(value) = _variable <- value //Assignment in F#

type ViewModel7() = 
    let _variable = ref 42
    member this.Foo 
        with get() = !_variable // same as _variable.Value
        and set(value) = _variable := value //Same as `_variable.Value <- value`

When to use mutable or ref

You should prefer mutable over ref as it is easier to with. You can use ref when using a framework that updates the value of properties in a subclass. To illustrate this, here is our next view model:

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
type ViewModel8() = 
    inherit FrameworkViewModel()

    let _variable = ref 42
    member this.Foo 
        with get() = !_variable
        and set(value) = 
            if (this.SetProperty(_variable, value)) then 
                this.RaisePropertyChanged("Foo")

They key part to the code above is the call to SetProperty a method on the base class. For a view model, there is no need to set a value if it has not changed. SetProperty performs this check, and then updates the value if value has changed. A bool, ie true, will be returned if the value is updated. A ref makes this possible.

Auto Properties: A new keyword

Auto properties have a backing variable. In F# the syntax is a little different to methods. Here is an example with a getter only

1: 
2: 
3: 
4: 
5: 
6: 
type ViewModel9() = 
    member val Foo = 42

// Long form, prefer the above format if no setter
type ViewModel10() = 
    member val Foo = 42 with get

The key difference here is the use of the keyword val. Additionally the instance, this no longer needs to declared before the name. Adding a setter is also super easy, just add a comma and the keyword set:

1: 
2: 
type ViewModel11() = 
    member val Foo = 42 with get, set

Scoping rules: There’s a twist

Scoping rules are also available in F#. Methods can have their usage restricted to only inside the class with private.

1: 
2: 
type ViewModel12() = 
    member private this.Foo() = 42

There is no scoping for protected as it creates potential problems with lamda functions accessing the base class. Instead use interfaces/higher-order functions.

Prefer function over methods

It turns out there is another way to achieve the same as a private method in F#. A function declared in a class in most circumstances will behave the same as a private method. Because of this, it is best to prefer functions over methods, as functions have better type inference and can be chained together easier using F#’s iconic piper operator |>.

1: 
2: 
3: 
4: 
type ViewModel13() = 
    let foo = 42
    let addTen x = x + 10
    let data = foo |> addTen |> string

Closely related to access modifiers, is overriding methods. It is also possible in F#, and is just a keyword change to use override instead of member

1: 
2: 
3: 
type ViewModel14() = 
    inherit MvvmBase()
    override this.Foo() = 42

Adding constructor arguments

Constructor arguments, are passed within the parentheses of the declaring line of the class. See below:

1: 
2: 
type ViewModel15(foo: int) = 
    member this.Foo: int = foo

Types in F# are declared after the name with a colon. The type can also be omitted in many cases and F# will infer the type.

1: 
2: 
type ViewModel16(foo) = 
    member this.Foo: int = foo

For this example, the method Foo has been declared to return an int. F# can figure out that the type of foo must also be an int. Alternatively, the type could be declared the other way around. The types and meaning are identical in both cases:

1: 
2: 
type ViewModel17(foo: int) = 
    member this.Foo = foo

Another important point to highlight with constructor arguments in F# is that they are immutable:

1: 
2: 
3: 
4: 
// Will not compile. Error: `This value is not mutable`
type ViewModel18(foo: int) = 
    let fooPlusTen = 
        foo <- 10

If immutability is a problem and mutation is required, there is a simple solution. A copy of the constructor argument can be taken and declared with the mutable keyword highlighted above (though many prefer to avoid mutation, as functional competence is gained).

1: 
2: 
3: 
4: 
type ViewModel19(foo: int) = 
    let mutable _foo = foo
    let fooPlusTen = 
        _foo <- 10

A Constructor without a Constructor

The syntax for constructors is a quite different in F#; though much cleaner. To declare a constructor, after the local functions/values and before any member items, do signals the constructor followed by the required statements.

1: 
2: 
3: 
4: 
5: 
6: 
type ViewModel20() = 

    let mutable foo: string = null
    do 
        foo <- "Hello, World"
    member this.Foo() = foo

In the example above, the class is initially constructed with foo set to null. The constructor (the statements after do), are evaluated and foo is updated to be “Hello, World”.

The example above only aims to highlight the usage of do, however usage of null and mutable are not encouraged. As a developer learns techniques in functional programming, usage of null or mutable is required less. The reason for the reduced usage is that concepts in functional programming, model computation differently resulting in code that is much clear in intent and fewer runtime errors. For a brief highlight of this see my post on How to turn runtime exceptions into compiler errors.

Dependencies with interfaces

If you have read this far, we’re almost at the point where all the knowledge has been laid out to build any standard view model. To complete this, you need to know how to pass in dependencies, notably interfaces.

An interface could be declared in C# or F# (The C# declaration would need to be in a different project). Here is one written in F#. There are no parenthesis after the name. Methods are declared with the abstract keyword and a type declaration.

1: 
2: 
type IService = 
    abstract member Foo: unit -> int

‘Foo’ is method that, when invoked, returns a single integer. With an interface declared, it can now be used. Here is a view model that is using the interface as a dependency. Simply declare the type and use it as you would any constructor argument.

1: 
2: 
type ViewModel21(foo: IService) = 
    member this.Foo: int = foo.Foo()

If you were to compare this to the C#/Java equivalent, you will see that a copy of the dependency does not need to be taken. No strange attributes, no local constructor parameters. No duplicate names.

Another key feature with these interfaces and view models is that they are fully compliant with any IOC framework. Register the interface (F# or C#), make sure the view model is appropriately named and enjoy everything working. These F# interfaces/classes generate very similar IL that the C# equivalent would output, you benefit from clearer code.

Interfaces are optional

The last example showed building a view model with a dependency through an interface. In large apps, both interfaces themselves and the number of interfaces a view model requires can get large. Many developers have found this can make things difficult to work with, and hard to refactor as the abstractions are no longer clear. There is an alternative that many developers have already turned to. Replace the interface with just a function. For a full read up on this Scott Wlaschin provides the details: Functional approaches to dependency injection. To follow the approach that Scott talks about, all we need to do is pass in functions.

1: 
2: 
3: 
4: 
5: 
type ViewModel22(foo) = 
    let data = foo()
    member this.Foo: int = data

let vm = ViewModel(fun () -> 42)  

As stated, no interface needed, just a function. Type inference checks the type for us so everything must be wired up correctly. Additionally, it must be noted that construction of the class must be done explicitly (Leave a comment if you know of an IOC container that can resolve functions). The result is that the code is now easier to read, especially for juniors since an IOC container is an advanced topic.

Using this functional approach will result in many functions passed into the view model. Incase it gets hard to keep track of those names and types, an alias for the type can be created to keep dependencies readable. Here’s an example with a couple of dependencies:

1: 
2: 
3: 
4: 
5: 
6: 
type IFoo =  unit -> int
type IBar =  unit -> string
type ViewModel23(foo: IFoo, bar: IBar) = 
    let data = foo() |> string
    let message = bar() + " , " + data
    member this.Foo = message

The type of foo and bar are declared explicitly, rather than being inferred by the compiler. As arguments to the class they now hve readable types as opposed to function types. The alias only affects readability, and will not help the compiler find errors.

So there you go, 23 absolutely useless view models. F# makes it easier and clear to create view models. The code is shorter and with a powerful compiler, both typing and errors can be reduced!

Your challenge: How many ways can you combine the 22 view models to make useful view models?

This post is licensed under CC BY 4.0 by the author.