The Real Story of a Junior Engineer Shipping F# Code to Production

Eduardo Lemos
Datarisk.io
Published in
7 min readJun 3, 2022

In this text, I will tell about my experience during the first 4 months of my very first job. After knowing that Datarisk uses a functional programming language to make software products, I got really excited: not only this would be my first experience shipping code to production, but it would be done utilizing the functional paradigm.

So, during these past few months, I was introduced to F# and its features to develop web applications. The main goal was, and still is, to use functional programming features to leverage popular web development tools, such as React.

After getting used to the F# syntax and its nuances and exploring the existing infra, my productivity reached the point where I started making serious contributions to current projects. Here I will tell the main points that made this possible.

The Type System

The wonders and the dangers of being an underdog

F# is a very recent technology. By sharing the .NET environment with C#, F# is a perfect candidate to build products whilst making use of functional programming features. The technology is at a sweet spot: not too mature in a way that it is hard to update it using community feedback, but not so fresh that there are still a lot of missing features.

This, however, has a side effect: the lack of documentation and descriptive examples. A lot of F# libraries do not have a proper document that describes what and how each function is doing its business, which can slow down the development and add unexpected behavior when using such libraries. Due to this absence, it is pretty common to open the source code and search by yourself if certain functionality is present and, if so, how it is being done.

The savior

This, however, had an unexpected solution: the type system.

Types work as your documentation.

A killer feature of F# is the static type system. A statically typed system checks for types in compile-time, i.e., before running the application the compiler will check for errors and assure type safety across the code base. This prevents a lot of headaches that usually happen in run time, i.e., when you are actually doing a demo of the product to the client.

When using a text editor or an IDE, the developer can integrate a language-server protocol tool (LSP). It provides programming language-specific features, such as the type signature of used functions. This is huge: during development, some intuition took place just by reading the type signature of a function included in a specific library. Although not equal to a proper description of its internal functionality, this shrinks down the overhead of how to use a function and which overloads it has available.

This was an unexpected bonus for a statically typed language: you not only get errors during compile time for type safety, but you also can use the type system as initial documentation to get things done quickly. Before this lesson, I was lost without knowing how to test new functions and fighting the compiler with type errors dominating my terminal screen. The type system, when combined with LSP, fulfills the role of giving me details and patterns in the tool I was using.

Feliz made me Happy

The standard way: Fable.React

At Datarisk, we use the Fable compiler to put Javascript back into sanity. This compiler translates F# code to JS code, so it can be understood by the browser whilst still allowing the developer to use the functional programming ecosystem. Furthermore, Fable also maps popular React libraries, such as React and Material UI — popular UXs for frontend development — hence increasing the options for the F# developer to achieve the same level of quality of popular JS implementations. The snippet below is an example of an HTML component written in F# using Fable.React:

open Fable.React
open Fable.React.Props

div [ Style [ CSSProp.Display DisplayOptions.Flex ] ] [
h1 [ Style [ CSSProp.Color "red" ] ] [
str "Text 1"
]
h3 [ Style [ CSSProp.Color "blue"] ] [
str "Text 2"
]
]

In this particular example, an HTML div, with an HTML h1 and h3 inside, is being created with a custom CSS for styling. In Fable, a given HTML component receives two lists: one that comprises properties and styling and another list that contains the component’s children. At first glance, this can seem like a good separation of concerns. Later, however, I discovered that it is inconvenient because all properties, with nature of all sorts, are combined into the same list.

Also, there is nothing indicating to the developer in the code the purpose of each one of these lists. It is assumed that developers, including newcomers, know how Fable.React organizes and separates components with their properties. The same happens with the list of children components: you have to know that this second list is dedicated to accumulating the parent’s children. Although it does not matter in the long term, this lack of descriptiveness in the code makes life harder with no justification for the extra hassle.

Alternative way: Feliz for the rescue

This, however, was not the end of the road. There is a step further to solve our problems: Feliz. This retake on the React DSL uses a different syntax, addressing all the aforementioned concerns in Fable.React. The same example from before is presented below, but written in Feliz this time around:

open Feliz

Html.div [
prop.style [ style.display.flex ]
prop.children [
Html.h1 [
prop.style [ style.color.red ]
prop.text "Text 1"
]
Html.h3 [
prop.style [ style.color.blue ]
prop.text "Text 2"
]
]
]

In Feliz, properties and styling are managed separately. Moreover, the list of children is now being set as a property. In this manner, for all configurations of the component, it is explicit the individual properties and what is its name, such as style, identifier, or children. So, it is common to see this pattern of three groups of information — various properties, the styling property, and children property. This DSL makes development easier, as well as more intuitive.

Not all is perfect: bindings and transpilation

However, Fable.React and Feliz are not perfect. There are imperfections deeply rooted in the compiler. For instance, some problems can occur when translating the F# code to JS, with weird behaviors popping up out of nowhere. Although rare, these situations can be problematic.

One common example of such a bug is when handling date times. During development, validation functions for date types didn’t work because this type is not properly converted to JS. This was a very obscure and time-consuming bug to uncover.

Also, related to translation and conversions, there are the bindings between these two domains — F# and JS. To take advantage of Fable, JS functions need to be manually mapped to the F# domain. In most cases, the communities responsible for developing the libraries handle this binding for future developers to use. But, sometimes a specific binding is not available and it is necessary to make the binding yourself, an activity that can demand time depending on the complexity of the target function. Below there’s a simple example of a binding to get a refresh or reload a page:

[<Emit("document.location.reload()")>]
let reloadPage () = jsNative

Sharing is Good

In parallel with learning F# and Fable/Feliz, I got in touch with the huge amount of code already in production. Although not polished, there were a lot of lessons learned on these lines of code, and all of that was being reflected in products. That was critical: the ability to read something that already works and has good abstractions is the best way to educate yourself about a new programming language or technology. Furthermore, you can compare different code sections and figure out which abstraction works best for each use case, bringing feedback to your team later. Discussions can be made to improve the stack in future refactoring pull requests.

Moreover, one of the main initiatives that the unit presented is pursuing sharable code, i.e., abstracting out code that is so generic that other products can take advantage of it. This is especially important because these abstractions are already tested and have been used in the real world, thus being likely re-utilizable. For instance, fields in forms are super common when creating a web application. This can be abstracted out of a product to a private library, hence allowing other teams to utilize all the brainstorming and dedication that another team did. Reusability is heavily focused across teams at Datarisk.

After playing around with the code base, I was comfortable enough to jump into a new initiative that I was allocated. Because I was already comfortable with the built library that the team built, in two weeks I was able to push a significant amount of new features, web pages, and functionalities to the new product. Further, because I was using more intensively such tools, mistakes and improvements became more apparent, giving me the chance of talking to specialists and making the abstraction better, i.e., more powerful, generic, and less error-prone.

This sharing process does not end at code and abstractions. Another valuable principle followed by the tech unit is the importance of sharing experience as a developer. Conversations with specialists brought to light aspects of working in a team that I was not putting much attention to, such as the importance of a good naming strategy and how to organize a project in an optimal way for other members. I corrected myself not only about programming concepts, such as understanding how the type system can act as documentation but also about how to be a better software engineer, absorbing the lessons acquired by elder developers, with golden thoughts accumulated over the years.

Final Thoughts

My conclusions after these past months are twofold: when dealing with new technology, practicing the syntax is totally underestimated; and absorbing sharable content from the already built infra/team is a must to speed up productivity. Without the former, I would not be comfortable with the tool I use daily and it would require more try and error to get things done. And without the latter, a lot of time would be spent thinking, creating, and validating existing abstractions. To convert all this time and effort into value for the company you are working on, ask for help and advice from your colleagues, they will definitely provide insights that required them years to learn and you will get that in a matter of minutes or hours.

If you want to know more about Datarisk and how it could generate more value for your business, visit us on www.datarisk.io.

--

--