F# Tutorial: How to Build a Full-stack F# App
In recent years, functional programming has gained a reputation as a particularly rigorous and productive paradigm. Not only are functional programming languages gaining attention within programmer communities, but many large companies are also starting to use functional programming languages to solve commercial problems.
For example, Walmart’s have begun using Clojure, a JVM-based functional Lisp dialect, for its checkout infrastructure; Jet.com, a large ecommerce platform (now owned by Walmart), uses F# to build most of its microservices; and Jane Street, a proprietary trading firm, primarily use OCaml to build their algorithms.
Today, we will explore F# programming. F# is one of the functional programming languages that is seeing increasing adoption due to its flexibility, strong .NET integration, and high quality of available tools. For the purposes of this F# tutorial, we will build a simple web server and a related mobile app using only F# for both the front end and the back end.
For today’s project, we will be using nothing but F#. There are several reasons to prefer F# as our language of choice:
· .NET integration: F# has a very tight integration with the rest of the .NET world and therefore ready access to a large ecosystem of well-supported and thoroughly-documented libraries for solving a wide range of programming tasks.
· Conciseness: F# is extremely concise due to its powerful type inference system and terse syntax. Programming tasks can often be solved much more elegantly using F# than C# or Java. F# code can appear very streamlined by comparison.
· Developer tools: F# enjoys strong integration Visual Studio, which is one of the best IDEs for the .NET ecosystem. For those working on non-Windows platforms, there is an abundance of plugins in visual studio code. These tools make programming in F# extremely productive.
I could go on about the benefits of using F#, but without further ado, let’s dive in!
In the United States, there is a popular saying: “It’s five o’clock somewhere”.
In some parts of the world, 5:00 pm is the earliest time when it is socially acceptable to have a drink, or a traditional cup of tea.
Today, we will build an application based on this concept. We will build an application that, at any given time, searches through the various time zones, find out where it is five o’clock, and provide that information to the user.
We will begin by making the back-end service that performs the timezone search function. We will use Suave.IO to build a JSON API.
Suave.IO is an intuitive web framework with a lightweight web server that allows simple web apps to be coded up very quickly.
To begin, go to Visual Studio and start a new F# Console Application project. If this option is not available to you, you may need to install F# functionality with Visual Studio Installer. Name the project “FivePM”. Once your application is created, you should see something like this:
[<EntryPoint>]
let main argv =
printfn "%A" argv
0 // return an integer exit code
This is a very simple piece of starter code that prints out the argument and exits out with status code 0. Feel free to change the print statement and experiment with various functions of the code. The “%A” formatter is a special formatter that prints the string representation of whatever type you pass in, so feel free to print integers, floats, or even complex types. Once you’re comfortable with the basic syntax, it’s time to install Suave.
The easiest way to install Suave is through the NuGet package manager. Go to Project -> Manage NuGet packages, and click on the browse tab. Search for Suave and click install. Once you accept the packages to install you should be all set! Now go back to your program.fs screen and we’re ready to begin building the server.
To begin using Suave, we will need to import the package first. At the top of your program, type the following statements:
open Suave
open Suave.Operators
open Suave.Filters
open Suave.Successful
This will import the basic packages required to build a basic web server. Now replace the code in the main with the following, which defines a simple app and serves it on port 8080:
[<EntryPoint>]
let main argv =
// Define the port where you want to serve. We'll hardcode this for now.
let port = 8080
// create an app config with the port
let cfg =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]}
// We'll define a single GET route at the / endpoint that returns "Hello World"
let app =
choose
[ GET >=> choose
[ path "/" >=> request (fun _ -> OK "Hello World!")]
]
// Now we start the server
startWebServer cfg app
0
The code should look very straightforward, even if you are not familiar with F# syntax or Suave’s way of defining route handlers, the code should be fairly readable. Essentially, the web app returns with a 200 status and “Hello World!” string when it is hit with a GET request on the “/” route. Go ahead and run the application (F5 in Visual Studio) and navigate to localhost:8080, and you should see “Hello World!” in your browser window.
Now we have a web server! Unfortunately, it does not do a whole a lot - so let’s give it some functionality! First, let’s move the web server functionality elsewhere so that we build some functionality without worrying about the web server (we will connect it to the web server later). Define a separate function thusly:
// We'll use argv later :)
let runWebServer argv =
// Define the port where you want to serve. We'll hardcode this for now.
let port = 8080
// create an app config with the port
let cfg =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]}
// We'll define a single GET route at the / endpoint that returns "Hello World"
let app =
choose
[ GET >=> choose
[ path "/" >=> request (fun _ -> OK "Hello World!")]
]
// Now we start the server
startWebServer cfg app
Now change the main function to the following and make sure that we did it right.
[<EntryPoint>]
let main argv =
runWebServer argv
0
Hit F5, and our “Hello World!” server should function like before.
Now let’s build out the functionality that determines the time zone where it is five o’clock. We want to write some code to iterate through all the time zones and determine the timezone that is closest to 5:00 pm.
Furthermore, we don’t really want to return a timezone that is very close to 5:00 pm but is slightly before (e.g. 4:58 pm) because, for the purposes of this demonstration, the premise is that it cannot be before 5:00 pm, however close.
Let’s begin by getting a list of timezones. In F# this is very easy since it integrates so well with C#. Add “open System” at the top, and change your F# application to this:
[<EntryPoint>]
let main argv =
// This gets all the time zones into a List-like object
let tzs = TimeZoneInfo.GetSystemTimeZones()
// Now we iterate through the list and print out the names of the timezones
for tz in tzs do
printfn "%s" tz.DisplayName
0
Run the application and you should see in your console a list of all the time zones, their offsets, and their display name.
Now that we have the list of time zones, we can convert them to a custom data type that is more useful to us, something that contains information like the UTC offset, local time, how far it is from 5:00 pm in local time, etc. To that end, let’s define a custom type, right above your main function:
type TZInfo = {tzName: string; minDiff: float; localTime: string; utcOffset: float}
Now we can transform and the timezone information we got from the last step into a list of this TZInfo objects. Change your main function thusly:
[<EntryPoint>]
let main argv =
// This gets all the time zones into a List-like object
let tzs = TimeZoneInfo.GetSystemTimeZones()
// List comprehension + type inference allows us to easily perform conversions
let tzList = [
for tz in tzs do
// convert the current time to the local time zone
let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz)
// Get the datetime object if it was 5:00pm
let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0)
// Get the difference between now local time and 5:00pm local time.
let minDifference = (localTz - fivePM).TotalMinutes
yield {
tzName=tz.StandardName;
minDiff=minDifference;
localTime=localTz.ToString("hh:mm tt");
utcOffset=tz.BaseUtcOffset.TotalHours;
}
]
printfn "%A" tzList.Head
0
And you should see the tzInfo object for Dateline Standard time printed to your screen.
Now that we have a list of these tzInfo objects, we can filter and sort these objects to find the time zone where it is 1) after 5:00 pm and 2) closest to 5:00 pm of the timezones in 1). Change your main function like so:
[<EntryPoint>]
let main argv =
// This gets all the time zones into a List-like object
let tzs = TimeZoneInfo.GetSystemTimeZones()
// List comprehension + type inference allows us to easily perform conversions
let tzList = [
for tz in tzs do
// convert the current time to the local time zone
let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz)
// Get the datetime object if it was 5:00pm
let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0)
// Get the difference between now local time and 5:00pm local time.
let minDifference = (localTz - fivePM).TotalMinutes
yield {
tzName=tz.StandardName;
minDiff=minDifference;
localTime=localTz.ToString("hh:mm tt");
utcOffset=tz.BaseUtcOffset.TotalHours;
}
]
// We use the pipe operator to chain functiona calls together
let closest = tzList
// filter so that we only get tz after 5pm
|> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0)
// sort by minDiff
|> List.sortBy (fun (i:TZInfo) -> i.minDiff)
// Get the first item
|> List.head
printfn "%A" closest
And now we should have the time zone that we’re looking for.
Now let’s refactor the code out to its own function so we can use it later. Define a function thusly:
// the function takes uint as input, and we represent that as "()"
let getClosest () =
// This gets all the time zones into a List-like object
let tzs = TimeZoneInfo.GetSystemTimeZones()
// List comprehension + type inference allows us to easily perform conversions
let tzList = [
for tz in tzs do
// convert the current time to the local time zone
let localTz = TimeZoneInfo.ConvertTime(DateTime.Now, tz)
// Get the datetime object if it was 5:00pm
let fivePM = DateTime(localTz.Year, localTz.Month, localTz.Day, 17, 0, 0)
// Get the difference between now local time and 5:00pm local time.
let minDifference = (localTz - fivePM).TotalMinutes
yield {
tzName=tz.StandardName;
minDiff=minDifference;
localTime=localTz.ToString("hh:mm tt");
utcOffset=tz.BaseUtcOffset.TotalHours;
}
]
// We use the pipe operator to chain function calls together
tzList
// filter so that we only get tz after 5pm
|> List.filter (fun (i:TZInfo) -> i.minDiff >= 0.0)
// sort by minDiff
|> List.sortBy (fun (i:TZInfo) -> i.minDiff)
// Get the first item
|> List.head
And our main function can just be:
[<EntryPoint>]
let main argv =
printfn "%A" <| getClosest()
0
Run the code and you should see the same output as before.
Now that we can get the timezone data, we can transform the information into JSON and serve it through our application. This is quite simple, thanks to the JSON.NET package from NewtonSoft. Return to your NuGet package manager and find Newtonsoft.Json, and install the package. Now return to Program.fs and make a small change to our main function:
[<EntryPoint>]
let main argv =
printfn "%s" <| JsonConvert.SerializeObject(getClosest())
0
Run the code now and instead of the TZInfo object, you should see the JSON printed to your console.
It is very simple to connect this to our JSON API. Simply make the following changes to your runWebServer function:
// We'll use argv later :)
let runWebServer argv =
// Define the port where you want to serve. We'll hardcode this for now.
let port = 8080
// create an app config with the port
let cfg =
{ defaultConfig with
bindings = [ HttpBinding.createSimple HTTP "0.0.0.0" port]}
// We'll define a single GET route at the / endpoint that returns "Hello World"
let app =
choose
[ GET >=> choose
[
// We are getting the closest time zone, converting it to JSON, then setting the MimeType
path "/" >=> request (fun _ -> OK <| JsonConvert.SerializeObject(getClosest()))
>=> setMimeType "application/json; charset=utf-8"
]
]
// Now we start the server
startWebServer cfg app
Run the application, and navigate to localhost:8080. You should see the JSON on your browser window.
Now that we have the JSON API server, we can deploy it so it is accessible on the internet. One of the easiest ways to deploy this application is through Microsoft Azure’s App Service which can be understood as a managed IIS service. To deploy to Azure App service, head over to the https://portal.azure.com and go to App Service. Create a new app, and navigate to the deployment center in your portal. The portal can be a bit overwhelming if it’s your first time, so if you have trouble be sure to consult one of the many tutorials out there for using App Service.
You should see a variety of options for deployment. You can use any that you like, but for the sake of simplicity, we can use the FTP option.
The App service looks for a web.config file at the root of your application to know how to run your application. Since our web server is an essential console application, we can publish the application and integrate with the IIS server using HttpPlatformHandler. In Visual Studio, add an XML file to your project and name it web.config. Fill it with the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers>
<remove name="httpplatformhandler" />
<add name="httpplatformhandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
</handlers>
<httpPlatform stdoutLogEnabled="true" stdoutLogFile="suave.log" startupTimeLimit="20" processPath=".\publish\FivePM.exe" arguments="%HTTP_PLATFORM_PORT%"/>
</system.webServer>
</configuration>
Connect to the FTP server using the credentials obtained from the deployment center (you will need to click on the FTP option). Move the web.config to the wwwroot folder of your app service FTP site.
Now we want to build and publish our application, but before we do, we need to make a small change to the server code. Go to your runServer function and change the first 3 lines to the following:
let runWebServer (argv:string[]) =
// Define the port where you want to serve. We'll hardcode this for now.
let port = if argv.Length = 0 then 8080 else (int argv.[0])
Which allows the application to look at the argument passed in and use the first argument as the port number rather than having the port be hard-coded to 8080. In the web config, we pass the %HTTP_PLATFORM_PORT% as the first argument, so we should be set.
Build the application in release mode, publish the application,
and copy the published folder to the wwwroot. Restart the application and you
should see the JSON API result at the *.azurewebsites.net
site.
Now our application is deployed!
Now that we have the server deployed, we can build a front end. For the front end, we will build an Android application using Xamarin and F#. This stack, like our back-end environment, enjoys deep integration with Visual Studio. Of course, the F# ecosystem supports quite a few front-end development options (WebSharper, Fable/Elmish, Xamarin.iOS, DotLiquid etc), but for the sake of brevity, we will only develop using Xamarin.Android for this post and leave them for future posts.
To set up the Android app, start a new project and select the Xamarin Android option. Make sure that you have Android development tools installed. Once the project is set up, you should see something like this in your main code file.
[<Activity (Label = "FivePMFinder", MainLauncher = true, Icon = "@mipmap/icon")>]
type MainActivity () =
inherit Activity ()
let mutable count:int = 1
override this.OnCreate (bundle) =
base.OnCreate (bundle)
// Set our view from the "main" layout resource
this.SetContentView (Resources.Layout.Main)
// Get our button from the layout resource, and attach an event to it
let button = this.FindViewById<Button>(Resources.Id.myButton)
button.Click.Add (fun args ->
button.Text <- sprintf "%d clicks!" count
count <- count + 1
)
This is the starter code for F# Android Xamarin. The code currently just keeps track of how many times a button was clicked and displays the current count value. You can see it work by hitting F5 to launch the emulator and starting the application in debug mode.
Let’s add some UI components and make it more useful. Open resource/layouts and navigate to Main.axml.
You should see a visual representation of the main activity layout. You can edit the various UI elements by clicking on the elements. You can add elements by going to the toolbox and selecting the element that you want to add. Rename the button and add a textView below the button. The XML representation of your AXML should look similar to this:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button android:id="@+id/myButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fivePM" />
<TextView
android:text=""
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView1" />
</LinearLayout>
The AXML makes reference to the strings resource file, so open your resources/values/strings.xml and make the following changes:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="fivePM">It\'s 5PM Somewhere!</string>
<string name="app_name">5PM Finder</string>
</resources>
Now we have built the front-end AXML. Now let’s connect it to some code. Navigate to MainActivity.fs and make the following changes to your onCreate function:
base.OnCreate (bundle)
// Set our view from the "main" layout resource
this.SetContentView (Resources.Layout.Main)
// Get our button from the layout resource, and attach an event to it
let button = this.FindViewById<Button>(Resources.Id.myButton)
let txtView = this.FindViewById<TextView>(Resources.Id.textView1);
button.Click.Add (fun args ->
let webClient = new WebClient()
txtView.Text <- webClient.DownloadString("https://fivepm.azurewebsites.net/")
)
Replace fivepm.azurewebsites.net with the URL of your own JSON API deployment. Run the application and click on the button in the emulator. In a little bit, you should see the JSON API return with your API result.
We’re almost there! Right now, our app is displaying the raw JSON and it’s rather unreadable. The next step, then, is parsing the JSON and outputting a more human-readable string. To parse JSON, we can use the Newtonsoft.JSON library from the server.
Navigate to your NuGet package manager and search for Newtonsoft.JSON. Install and go back to the MainActivity.fs file. Import it by adding “open Newtonsoft.Json”.
Now add the TZInfo type to the project. We could reuse the TZInfo from the server, but since we actually only need two of the fields, we can define a custom type here:
type TZInfo =
{
tzName:string
localTime: string
}
Add the type definition above the main function, and now change the main function thusly:
button.Click.Add (fun args ->
let webClient = new WebClient()
let tzi = JsonConvert.DeserializeObject<TZInfo>(webClient.DownloadString("https://fivepm.azurewebsites.net/"))
txtView.Text <- sprintf "It's (about) 5PM in the\n\n%s Timezone! \n\nSpecifically, it is %s there" tzi.tzName tzi.localTime
)
Now the JSON API result is deserialized into the TZInfo object and used to construct a string. Run the app and click the button. You should see the formatted string pop onto the screen.
While this application is very simple and perhaps unrefined, we have built a mobile application that consumes and F# JSON API, transforms the data, and displays it to the user. And we did it all in F#. Feel free to play around with the various controls and see if you can improve the design.
And there we have it! A simple F# mobile application and an F# JSON API and tells the user where it is five o’clock.