When introduced to SAFE Stack applications, many developers initially recoil at the use of F# to represent your user interface - it looks unusual, especially if you come from an HTML-focused background. For those who haven't used it and F# before, the initial skepticism is often simply "it surely can't work!"
So, here are some of the main reasons why (as opposed to a mixture of HTML and another language using another paradigm) you should seriously consider adopting F# for rendering your web application views, whether that's server-side through something like Giraffe's View Engine or, in particular, client-side through Fable's React wrapper and the Elmish library.
Whether you're an F# developer who wants to develop full-stack applications, or the leader of a team of F# developers whom you'd like to solve problems also on the front-end, Giraffe's View Engine and Elmish will benefit you greatly! By utilizing F# on the front-end and UI code, these technologies make it possible for F# developers to smoothly adapt to full-stack web application development with very little upskilling necessary. Even though you're writing F# code, you are not missing out on the ability to go a level deeper into the HTML, since the idea is to emit HTML from F#. One to one.
Here is how you define a header inside a div in F# using Giraffe View Engine:
div  [
h1  [ str "I'm a header!" ]
...and here is the HTML code being generated by it:
<h1>I'm a header!</h1>
It's worth noting that it's not only your skills that you can reuse. If you're using different languages for different parts of your application (backend vs front-end), this might limit you to be able to use certain functions/methods only on the backend and certain ones only on the frontend. For example, you might be able to only create data access queries and business logic on the backend, and only create UI components on the front-end. Using F# for both front-end and back-end means this wall is demolished. If you so choose, you can create server-side functions that return UI components or use .NET libraries and make a call to the database within your UI code. This is not a limit but rather a precursor to how far you can take this.
Writing your UI code in F# gives you the freedom to use conditional logics on the go. Let's say that if a
condition is true you want to display a
h1 element, but if not, you rather want to generate a span with some text in it.
div [ ] [
if condition then
h1  [ str "I'm a header!" ]
span  [ str "I'm a span!" ]
This is not of course all you can use that freedom for, and as your application gets increasingly more complicated, you will appreciate this unity of conditional logic and UI components more and more. This snippet is sufficient, however, to demonstrate how easy and convenient it is to use conditional logic in your UI code. Combine this with many other F# features that makes the language so powerful, such as discriminated unions and pattern matches, and you have something truly powerful.
Let's say we want to use a div element more than once in our code, but we also want it to have a different background color, a different
on click action and a different text each time. This is very easy with F#. Let's define a function that takes in the color, an event and the text. It will render them into the button accordingly and return the button element.
let makeDiv text color onClick =
Style [ BackgroundColor color ]
With this function in place, creating div elements with different properties and content one after the other is as simple as calling it with different colors, functions and string values.
makeDiv "First Div" IsGray aFunction
makeDiv "Second Div" IsWarning anotherFunction
makeDiv "Third Div" IsPrimary someOtherFunction
This is a rather small example, but understanding the pattern is important. Combined with the way we can freely use conditional logic as I've demonstrated previously, it shouldn't be so hard to picture a scenario where you have a list of records (think C# objects) containing data and you write a logic that is based on the fields of the record determines what color the background should be, what the action of the
on click event should be and what to display as the text.
Composition is one of the key strengths of functional programming, and there is absolutely no need to get nervous when approaching this topic. Let me show you how simple it is and how useful it can be when writing UI code. Say that we have a function that takes in a string creates a header with this string:
let makeHeader text = h1  [ str text ]
...and another function that takes in a header, some content, and the background color to create a banner.
let makeBanner content color header =
div [ Style [ BackgroundColor color ] ] [
These two functions in place, it's as easy to create a banner in your UI as this:
Notice how we're using the
makeHeader function at the last line to create a header, and then passing this into the
makeBanner function? You could consider this a form of composition. Alternatively, we could actually "compose" the two functions and pass the header to it later on, too.
let contentWithHeader = makeHeader >> makeBanner content IsSecondary
contentWithHeader function will return the banner for which we have already passed in the content and the color if we simply give it a string to be rendered as the header.
It will do this by passing this string to the
makeHeader function, and then passing its result (a header element) to the
makeBanner function for which the content and color is already passed in.
F# community is quite lively when it comes to developing frameworks and libraries. I've actually used one of them (Fulma) when I declared the background color for the div. Fulma is an F# wrapper around Bulma, which is CSS framework based on flexbox that reduces the time and experience needed to develop responsive user interfaces by providing classes that we can add to the HTML elements. For example, you have a button that you want to increase the size of. You would add the "is-large" class to the button element.
<button class="is-large">I'm a large button!</button>
And just like that, the size of your button is increased. Bulma provides many different classes that are helpful for creating different components. Fulma allows us to make use of these classes in a type-safe manner. This F# code would produce the exact same result:
Button.button [ Button.Size IsLarge ] [ str "A button" ]
There are many F# wrappers for a huge variety of libraries which we can use in a consistent, discoverable and type-safe manner in F#. For the purposes of this blog post, here are a few of such libraries for UI programming:
Additionally, I highly encourage you to watch this video Isaac has put together recently that covers creating a web user interface: