The world of mobile app development clearly is dominated by object-oriented concepts. But does it have to be that way forever? This article discusses three different approaches to write Xamarin apps in F#.
Published on Tue, October 29, 2019
Xamarin apps can, in theory, be written in any language that compiles to IL and is therefore supported by the .NET platform. F# is one of those languages, and it is possible to use it as a replacement of the de-facto standard language for Xamarin, C#. For example, Charles Petzold and Greg Shackles have shown how to do this.
While a developer can benefit from the strengths of F#, some obvious obstacles might let feel using the language in this context counter-intuitive especially to functional programmers.
First of all, Xamarin works on top of highly object-oriented APIs of iOS and Android, and Xamarin.Forms is designed in an object-oriented way as well. In order to implement, for example, native view controllers (iOS), activities (Android), or pages (Xamarin.Forms), it is necessary to use OOP concepts such as inheritance. Due to the nature of object-oriented APIs, much state-mutation is necessary in order to make things work. When configuring the properties of an UILabel
control on iOS, for example, this is done through setting its properties and therefore mutating the label's state.
[<Register("ViewController")>]
type ViewController(handle : IntPtr) =
inherit UIViewController(handle)
override x.ViewDidLoad() =
base.ViewDidLoad()
let label = new UILabel(x.View.Frame)
label.BackgroundColor <- UIColor.Yellow // Mutation
label.Text <- "Hello World" // Mutation
x.View.Add label
()
Those obstacles become even more evident when the app is built with an object-oriented architecture style like MVVM. In order to make bindings between view and view model possible, properties of the view model are being updated, and their state is being changed frequently.
All in all, it can be stated that F# can be used to replace C#, but the benefits most likely will not outweigh the disadvantages.
Instead of using a functional-first language for mainly object-oriented tasks, an alternative approach is to use both paradigms and languages side-by-side. Everything related to UI, including views and view models, can still be written in C#, leveraging its natural object-oriented capabilities in conjunction with the object-oriented frameworks the mobile app environment provides. However, everything "below," in particular the model part of MVVM, can be written in F# in a functional style.
One example could be my own implementation of a local Redux store written in F#, which sits at the core of a C# Xamarin application. F# is favored for this specific task over C# because of its built-in features regarding immutability, structural equality, and its discriminated union type. Features that are not available through C# or that would come with additional costs attached. However, the sole consumer of the store's features is the C# mobile app, which is built "around" it. That is possible because, as lined out before, everything in .NET compiles eventually to IL code. So, F# and C# projects can be used side-by-side within a single "hybrid" solution.
The F# compiler has some characteristics that make it even more convenient to use F# code from within C#. As Jon Skeet and Tomas Petricek mention, "classes or records with members, ... appear as standard C# classes and can be used without any trouble."
type Article =
{ Title: string
Author: string }
The F# Article record type can be constructed in C# as any other C# class.
var article = new Article("Hello World", "Jane Doe");
It is worth noting that the characteristics of an F# record are preserved even when used from C#. All of its properties need to be provided during construction; afterwards, they are immutable. On top of that, structural equality is provided as well.
One of the most powerful features of F# that C# is still lacking is the support of discriminated unions. However, discriminated unions "are nothing but a bunch of classes generated by the F# compiler" (source).
type ArticleType =
| Editorial
| Column of columnist: string
| Essay
For the payload case, the compiler creates a factory method that helps to create the object in C#.
var column = ArticleType.NewColumn("Jane Doe");
After all, it is not only technically feasible to use F# and C# side-by-side, but this approach also offers an opportunity to introduce functional programming with F# for many software development teams in the first place. Existing object-oriented knowledge, concepts, and code artifacts can continue to be used while solving specific problems with functional programming at the same time.
In 2012, Evan Czaplicki presented Elm, a new programming language that focuses on building purely functional graphical user interfaces. The language has evolved since then and managed to build an active community of users who primarily build web applications with it.
While the community of Elm developers was growing, and more and more applications were developed with it, a specific pattern has been discovered. What today is widely called The Elm Architecture, or MVU for Model-View-Update outside of the Elm ecosystem, "seems to emerge naturally in Elm. Rather than someone 'inventing' it, early Elm programmers kept discovering the same basic patterns in their code."
MVU has since been part of a movement towards architectures supporting unidirectional dataflows for user interfaces.
Unlike other architectural patterns like MVVM, "the Model" in MVU does not stand for an unspecified set of services and utilities, but for a very specific data structure that contains the (whole) state of the application. This data structure is immutable.
For rendering the view, the model is passed to a view function that returns the UI based on that exact model. This function is pure, which makes it possible to unit-test it — something hard to achieve or even impossible in most alternative solutions and especially for XAML UIs.
Of course, users want to interact with the interface, so it is not static and needs to react to input. That is done through commands, which eventually dispatch messages which are then being processed by an update function.
Update functions are pure, too. They take in the current model and a message and return a modified (updated) copy of the model. The update is being performed based on the message. Whenever a message is being dispatched, and therefore a new model is being created by the update function, the view is being re-rendered.
All of this ensures that data flows only in one direction through the whole application. This makes it very easy to reason about the program, it makes its components testable, and it enables features like time-travel debugging. As Wolfgang Loder notes, this is made possible through much wiring that is automatically being done in the background:
This makes it a little bit difficult at first to understand what is going on, but once the concept is clear we see that it reduces code in our application significantly.
Thanks to Fable, JavaScript has been an attractive compilation target for F# developers for many years. Consequently, in 2016, Eugene Tolmachev announced the first version of what he called Elmish: an implementation of MVU for Fable.
In 2018, Don Syme was working together with the Xamarin team in his role as a researcher at Microsoft Research, trying to find out if there was a way to make app development with Xamarin as compelling to F# developers as it was to build web applications (see his talk on the matter). The existing solutions did not convince him: XAML seemed complicated and even unnecessary to him, and all the MVVM approaches were based on mutable data.
Inspired by Fable and Elmish, Syme focused his research on a solution that could deliver a developer experience which was comparably easy and functional-first. He eventually came up with a library called Fabulous, an open-source project which is not affiliated to Microsoft but developed by its community of volunteers. As of today, Fabulous allows building mobile applications in a functional way on top of Xamarin.Forms by offering two different flavors: "Full Elmish" and "Half Elmish."
When choosing Full Elmish, the entire Xamarin application can be written in F# following the original idea of The Elm Architecture:
type Model = { Count : int }
type Msg = | Increment | Decrement
let init () = { Count = 0; }, Cmd.none
let update msg model =
match msg with
| Increment -> { model with Count = model.Count + 1 }, Cmd.none
| Decrement -> { model with Count = model.Count - 1 }, Cmd.none
let view (model: Model) dispatch =
View.ContentPage(content =
View.StackLayout(children = [
View.Label(text = sprintf "Current Count: %d" model.Count)
View.Button(
text = "Increment",
command = (fun () -> dispatch Increment))
View.Button(
text = "Decrement",
command = (fun () -> dispatch Decrement))]))
The sample shows an (almost) complete Fabulous program, including all essential parts: model, messages, and the three functions to initialize and update the model and to render the view.
The model, in this case, contains just a simple count property which is initialized to 0
. There are precisely two operations that can be done with this app: depending on the intent, which is expressed by a message, a new model is being created through the update function, with the count property either increased by one or decreased by one.
The view function takes in the model and a dispatch function. This allows creating a hierarchy of view elements depending on the model. In this simple case, the model's count value is rendered as a label. By using the dispatch function, updates can be triggered when the user interacts with the app through pressing either the increment or the decrement button.
Fabulous for Xamarin.Forms supports almost all of its elements, which can be used in its terse DSL to express the view in a very similar hierarchical way as it would be done traditionally through XAML. The key difference is that there are no bindings that react to changes inside of the view.
However, the view is being re-evaluated as soon as the model changes. To provide adequate rendering performance, the evaluation of views is handled by Fabulous in a way that allows a developer to specify custom differential updates for specific scenarios. So not the whole (complex) view is being re-rendered all the time but only those parts that need to reflect a change.
As defining UI in code quickly becomes a tedious process when every change would require a complete compilation and deployment cycle, Fabulous offers a mechanism it calls "Live Update". Live Update will send changes made to the code to the device or simulator/emulator, where the code is then evaluated, and the app is being refreshed immediately.
What developers get for free with Fabulous is the ability to unit-test most of the critical parts of an app, as those parts are implemented as pure functions that do not have any side-effects. The view, for example, can be tested by passing a mocked model to it. This is a unique advantage of the architecture and the Fabulous library, something hardly possible with XAML.
Some operations need to involve side-effects, like loading data from a database, or making network requests. Those operations are implemented through commands. Both the init
and the update
function return a tuple containing the model and a command. If there is something else returned than Cmd.none
, that command is being executed by Fabulous. The command itself is implemented as a function that can take in any parameter and almost always returns a new message which will then be passed to the update function again. This way, the – unidirectional – message loop stays intact.
Developers with a background in XAML and C# may be reluctant to choose the Full Elmish approach, especially because of the view part. Half Elmish offers a compromise: Views can still be created (or re-used from existing solutions) in XAML, while model and update function are being written in F#, next to a view function that wires up the bindings. This way, existing knowledge and code can be transferred, and the transition from XAML + C# to an application entirely written in F# is being made more convenient.
All three of the shown approaches have their cons and pros. While substituting C# by F# may be technically possible, it does not seem to make much sense to apply object-oriented concepts in a functional-first language. Mixing both C# and F# in a hybrid solution could be a good start, combining the strengths of both languages and paradigms. However, for most apps there won't be that much code to write besides views and view models. So if you really want to leverage F#, I suggest to take a look at Fabulous.