{"id":1213,"date":"2024-09-09T18:44:14","date_gmt":"2024-09-09T18:44:14","guid":{"rendered":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2024\/09\/09\/why-is-f-code-so-robust-and-reliable\/"},"modified":"2024-09-09T18:44:14","modified_gmt":"2024-09-09T18:44:14","slug":"why-is-f-code-so-robust-and-reliable","status":"publish","type":"post","link":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/2024\/09\/09\/why-is-f-code-so-robust-and-reliable\/","title":{"rendered":"Why is F# code so robust and reliable?"},"content":{"rendered":"<p>This is a guest post by Vladimir Shchur, lead developer at <a href=\"https:\/\/accesssoftek.com\/\">Access Softek<\/a>. Vladimir is an active open-source contributor, the author of <a href=\"https:\/\/github.com\/Lanayx\/Oxpecker\">Oxpecker<\/a> and <a href=\"https:\/\/github.com\/fsprojects\/pulsar-client-dotnet\">Pulsar.Client<\/a> libraries. He has also published a series of posts on <a href=\"https:\/\/medium.com\/@lanayx\">Medium<\/a> and <a href=\"https:\/\/github.com\/dotnet\/fsharp\/pull\/17277\">contributed to F# Core<\/a>.<\/p>\n\n<p>In Access Softek, we\u2019ve been developing software for financial institutions using C# and .NET for two decades, at the same time suffering from lots of bugs. We struggled to implement the <a href=\"https:\/\/www.ministryoftesting.com\/articles\/zero-bug-policy-the-myths-and-the-reality\">Zero Bug Policy<\/a> and had the green light to build one of our new projects, namely <a href=\"https:\/\/accesssoftek.com\/easycoin\/\">EasyCoin<\/a>, in F#, as it was claimed to be a very robust and effective tool.<\/p>\n<p>The EasyCoin project was challenging, requiring the implementation of distributed transactions with all the possible unexpected outcomes that could happen along the way. The transaction flow included integration with several services, including some internal SOAP ones. The project also required creating a client-facing UI, a web API, an admin site, and a few other elements. The implementation took us about a year and a half for the team of 4 F# full-stack developers, a team lead, a PM and a QA.<\/p>\n<p>The product has been live for a year, and remarkably, only one user-facing bug has been found so far. I attribute this success to the chosen language. In this post, I will share the specific reasons F# allowed us to write robust code. Some of them may be not applicable to your projects and this also might not mention F# aspects which your company would want to leverage. The following are the particular features that made our company avoid bugs in the software we do and hence make our customers happy.<\/p>\n<h2>Immutability by default<\/h2>\n<p>F#\u2019s basic blocks \u2014 values and records \u2014 are immutable by default (as in, in F#, it\u2019s easier to write immutable code than mutable code). This is good for thread safety and general code predictability \u2013 since objects don\u2019t change state, components can rely on them without worrying about how other parts of the program might alter them:<\/p>\n<p>let x = 1<br \/>\nx &lt;- 2                      \/\/ error, x is not mutable<\/p>\n<p>type User = { Id: int }<br \/>\nlet process (user: User) =<br \/>\n    user.Id &lt;- 2            \/\/ error, Id field is not mutable<br \/>\n    user &lt;- { Id: 2 }       \/\/ error, user argument is not mutable<\/p>\n<h2>Discriminated unions with exhaustive check<\/h2>\n<p><a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/discriminated-unions\">Discriminated unions (DU)<\/a> is an F# key feature which lets types hold a closed set of arbitrary data. Importantly, DU match is exhaustive, meaning that adding a case would raise warnings in all not yet handled cases, preventing potential bugs:<\/p>\n<p>open System<\/p>\n<p>type LoginType =<br \/>\n    | Password of string<br \/>\n    | PinCode of int<\/p>\n<p>let printLoginType loginType =<br \/>\n    match loginType with  \/\/ warning, not all cases are handled<br \/>\n    | Password password -&gt; Console.WriteLine(password)<\/p>\n<h2>No nulls by default<\/h2>\n<p>While many modern languages added some control for doing more null checks to prevent NullReferenceException, F# avoided them from the inception. This means such exceptions are nearly impossible in the end-to-end F# workflows (although, you still need to handle them during interop):<\/p>\n<p>type User = { Id: int }<br \/>\nlet user = { Id = null }     \/\/ error, null is not assignable to int<br \/>\nlet user: User = null        \/\/ error, null is not assignable to record User<\/p>\n<p>let s1: string = null        \/\/ allowed in F# 8 due to interop with C#, warning in F# 9 (when opted in)<br \/>\nlet s2: string | null = null \/\/ allowed in F# 9 due to interop with C# and null reference type support (when opted in)<\/p>\n<p><em>Idiomatic<\/em> F# way to deal with missing values is using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/options\">Option&lt;&#8216;T&gt;<\/a> type, so that the developer has to explicitly unwrap it and handle the missing value:<\/p>\n<p>open System<\/p>\n<p>let numberOption = [1; 2; 3] |&gt; List.tryFind (fun x -&gt; x % 2 = 0)<\/p>\n<p>match numberOption with<br \/>\n| Some x -&gt; Console.WriteLine($&#8221;Even number was found: {x}&#8221;)<br \/>\n| None -&gt; Console.WriteLine(&#8220;Even number was not found&#8221;)<\/p>\n<h2>No exceptions in the business logic<\/h2>\n<p>Throwing custom exceptions is one of the popular ways to deal with errors in business logic, however it is also a source of bugs, since there is no way to ensure those exceptions are handled properly. While it is still reasonable to throw exceptions in unexpected cases (like when the database connection fails), you shouldn\u2019t do it in the middle of the business transaction. F# has a dedicated <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/results\">Result&lt;&#8216;T&gt;<\/a> type for handling errors, and you can also easily define your more specific DUs:<\/p>\n<p>type TransactionStatus =<br \/>\n    | Ready<br \/>\n    | Pending<\/p>\n<p>let executeTransaction transaction =<br \/>\n    if validate transaction then<br \/>\n        try<br \/>\n            let isReady = callExternalService transaction<br \/>\n            if isReady then Ok Ready else Ok Pending<br \/>\n        with ex -&gt;<br \/>\n            Error $&#8221;Service is unavailable: {ex.Message}&#8221;<br \/>\n    else<br \/>\n        Error &#8220;Transaction is invalid&#8221;<\/p>\n<h2>Strict dependency order<\/h2>\n<p>In F#, all variables, functions, types and files can only depend on variables, functions, types and files defined earlier. The benefits of this are the fact that a circular dependency is not possible by default and extra clarity with \u201cwhat depends on what\u201d, which helps during code analysis and PR reviews:<\/p>\n<p>open System<\/p>\n<p>type Person = { BillingAddress: Address } \/\/ error, since the required type is defined below<br \/>\ntype Address = { Street: string }<\/p>\n<p>helloWorld() \/\/ error, the function is defined below<br \/>\nlet helloWorld() = Console.WriteLine(&#8220;Hello&#8221;)<br \/>\n&lt;ItemGroup&gt;<br \/>\n    &lt;Compile Include=&#8221;Operations.fs&#8221; \/&gt; &lt;!&#8211; error, since operations use models defined below &#8211;&gt;<br \/>\n    &lt;Compile Include=&#8221;Models.fs&#8221; \/&gt;<br \/>\n&lt;\/ItemGroup&gt;<\/p>\n<h2>Warnings on unused expression results<\/h2>\n<p>F# warnings will make you think twice about dangling values, where other popular languages would simply ignore that fact, unless they had an external analyzer:<\/p>\n<p>open System.Collections.Generic<\/p>\n<p>let people = Dictionary&lt;string, int&gt;()<br \/>\npeople.Add(&#8220;Jack&#8221;, 1)<br \/>\npeople.Remove(&#8220;John&#8221;) \/\/ warning, Remove returns bool value, you need to think about missing case<\/p>\n<h2>Typed primitives<\/h2>\n<p>F# has a notion of <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/units-of-measure\">Units of Measure<\/a> which help the compiler to verify that arithmetic relationships have correct units. Among other things, this (together with <a href=\"https:\/\/github.com\/fsprojects\/FSharp.UMX\">FSharp.UMX<\/a> package), make it very easy to have your primitives strongly typed with zero cost at runtime:<\/p>\n<p>open FSharp.UMX<\/p>\n<p>[&lt;Measure&gt;] type private tenantId<br \/>\n[&lt;Measure&gt;] type private transactionId<\/p>\n<p>type TenantId = string&lt;tenantId&gt;<br \/>\ntype TransactionId = string&lt;transactionId&gt;<\/p>\n<p>type Transaction = {<br \/>\n    TenantId: TenantId<br \/>\n    TransactionId: TransactionId<br \/>\n}<\/p>\n<p>let getTransactions tenantId transactionId =<br \/>\n    [<br \/>\n        { TenantId = tenantId; TransactionId = transactionId }<br \/>\n        { TenantId = tenantId; TransactionId = tenantId } \/\/ error, TenantId and TransactionId are different types<br \/>\n    ]<\/p>\n<p>getTransactions %&#8221;myTenant&#8221; %&#8221;1234&#8243;<\/p>\n<h2>Explicit conversions<\/h2>\n<p>Implicit type conversions are a common source of accidental errors. For example, this code will compile just fine in languages such as TypeScript or C#:<\/p>\n<p>var x = 1 + &#8220;&#8221;;       \/\/ compiles<\/p>\n<p>F# compiler won\u2019t allow such implicit conversions and will produce an error:<\/p>\n<p>let x = 1 + &#8220;&#8221;        \/\/ error, the type &#8216;string&#8217; does not match the type &#8216;int&#8217;<br \/>\nlet y = string 1 + &#8220;&#8221; \/\/ ok<\/p>\n<p>Another prominent example of accidental conversion bugs is string interpolation. Let\u2019s say we had this C# code:<\/p>\n<p>var age = 21;<br \/>\nvar displayAge = $&#8221;Your age is {age}&#8221;;<\/p>\n<p>Then someone refactored the code and used anonymous type, but forgot to update the interpolation:<\/p>\n<p>var age = new { Age = 21 };<br \/>\nvar displayAge = $&#8221;Your age is {age}&#8221;; \/\/ no errors or warnings, but wrongly evaluates to &#8220;Your age is { Age = 21 }&#8221;<\/p>\n<p>With F#, you can <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/plaintext-formatting\">specify<\/a> the type of the \u201chole\u201d to avoid such errors:<\/p>\n<p>let age = 21<br \/>\nlet displayAge = $&#8221;Your age is %i{age}&#8221;             \/\/ ok<\/p>\n<p>let ageRecord = {| Age = 21 |}<br \/>\nlet displayAgeRecord = $&#8221;Your age is %i{ageRecord}&#8221; \/\/ compilation error, ageRecord is not an integer<\/p>\n<h2>Functional approach to concurrency<\/h2>\n<p>Reading and writing code with locks is difficult, so actors frameworks are there for help. <a href=\"https:\/\/en.wikipedia.org\/wiki\/Actor_model\">Actors<\/a> simplify reasoning and reduce shared data bugs by assigning a single owner to data and communicating with each other using messages. Each actor should be able to handle different types of message and they can be conveniently modeled as Discriminated Unions.<\/p>\n<p>F# has a built-in actor support using <a href=\"https:\/\/fsharp.github.io\/fsharp-core-docs\/reference\/fsharp-control-fsharpmailboxprocessor-1.html\">MailboxProcessor&lt;&#8216;Msg&gt;<\/a>, but you can also easily switch to a more modern <a href=\"https:\/\/learn.microsoft.com\/dotnet\/core\/extensions\/channels\">Channels library<\/a>:<\/p>\n<p>open System<br \/>\nopen System.Threading.Channels<\/p>\n<p>type MyMessage =<br \/>\n    | Start<br \/>\n    | Print of string<br \/>\n    | Stop<\/p>\n<p>let actor = Channel.CreateUnbounded&lt;MyMessage&gt;(UnboundedChannelOptions(SingleReader = true))<br \/>\nlet actorLoopTask = backgroundTask {<br \/>\n    let mutable continueLoop = true<br \/>\n    while continueLoop do<br \/>\n        match! actor.Reader.ReadAsync() with<br \/>\n        | Start -&gt; Console.WriteLine(&#8220;Started&#8221;)<br \/>\n        | Print s -&gt; Console.WriteLine(s)<br \/>\n        | Stop -&gt; continueLoop &lt;- false<br \/>\n}<\/p>\n<p>actor.Writer.TryWrite Start |&gt; ignore                 \/\/ send 1st message to the actor<br \/>\nactor.Writer.TryWrite (Print &#8220;Hello world&#8221;) |&gt; ignore \/\/ send 2nd message to the actor<\/p>\n<p>actorLoopTask.Wait()<\/p>\n<h2>Explicit dependency injection<\/h2>\n<p>OOP projects usually use DI containers for injecting dependencies at runtime, thus dependency resolution bugs often reach production, because real dependencies are not covered by unit tests and missing registration can\u2019t be caught at compile time. With functional architecture, dependencies are passed explicitly as function arguments, which results in:<\/p>\n<p>Compile time verification, no \u201cunregistered dependency\u201d errors are possible at runtime<br \/>\nBetter separation of concerns, different functions only depend on their arguments, not on shared fields (like in constructor injection)<br \/>\nEasier testing, since all you need to do is to specify function arguments<\/p>\n<p>Dependency injection in F# is fully described in <a href=\"https:\/\/medium.com\/@lanayx\/dependency-injection-in-f-the-missing-manual-d376e9cafd0f\">this article<\/a>. Example code:<\/p>\n<p>let notifyUser (env: #IGetUserSettings &amp; #ISendEmail &amp; #ISendSms)<br \/>\n               userId message =<br \/>\n    task {<br \/>\n        let! userSettings = env.GetUserSettings(userId)<br \/>\n        match userSettings.NotificationType with<br \/>\n        | Email address -&gt;<br \/>\n            return! env.SendEmail(address, message)<br \/>\n        | Sms phone -&gt;<br \/>\n            return! env.SendSms(phone, message)<br \/>\n    }<\/p>\n<h2>The bottom line<\/h2>\n<p>These are just some of the ways that F# helped us write robust and readable code that dropped our bug count to nearly zero. What\u2019s more, F# libraries let you write type-safe SQL (using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/tutorials\/type-providers\/\">Type Providers<\/a>), type-safe HTML or IaC (using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/computation-expressions\">Computational Expressions<\/a>), type-safe ASP.NET route parameters (using <a href=\"https:\/\/learn.microsoft.com\/dotnet\/fsharp\/language-reference\/plaintext-formatting\">printf module<\/a>), and many more. And it is also worth mentioning that F# brings its goodness not just to .NET, but can run on other platforms with transpilers available to JavaScript and Python using the <a href=\"https:\/\/fable.io\/\">Fable project<\/a>.<\/p>\n<p>With that, I\u2019m wrapping up, I hope this article has provided valuable insights and inspired you to explore F# if you haven\u2019t done so already. F# community will be always ready to help in <a href=\"https:\/\/amplifyingfsharp.io\/\">Amplifying F#<\/a>, <a href=\"https:\/\/fsharp.org\/guides\/slack\/\">Slack<\/a>, <a href=\"https:\/\/discord.com\/invite\/R6n7c54\">Discord<\/a>, or <a href=\"https:\/\/x.com\/hashtag\/fsharp?f=live\">X<\/a>.<\/p>\n<p>The post <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\/why-is-fsharp-code-so-robust-and-reliable\/\">Why is F# code so robust and reliable?<\/a> appeared first on <a href=\"https:\/\/devblogs.microsoft.com\/dotnet\">.NET Blog<\/a>.<\/p>","protected":false},"excerpt":{"rendered":"<p>This is a guest post by Vladimir Shchur, lead developer at Access Softek. Vladimir is an active open-source contributor, the [&hellip;]<\/p>\n","protected":false},"author":0,"featured_media":0,"comment_status":"","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"site-sidebar-layout":"default","site-content-layout":"","ast-site-content-layout":"default","site-content-style":"default","site-sidebar-style":"default","ast-global-header-display":"","ast-banner-title-visibility":"","ast-main-header-display":"","ast-hfb-above-header-display":"","ast-hfb-below-header-display":"","ast-hfb-mobile-header-display":"","site-post-title":"","ast-breadcrumbs-content":"","ast-featured-img":"","footer-sml-layout":"","ast-disable-related-posts":"","theme-transparent-header-meta":"","adv-header-id-meta":"","stick-header-meta":"","header-above-stick-meta":"","header-main-stick-meta":"","header-below-stick-meta":"","astra-migrate-meta-layouts":"default","ast-page-background-enabled":"default","ast-page-background-meta":{"desktop":{"background-color":"var(--ast-global-color-4)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"ast-content-background-meta":{"desktop":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"tablet":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""},"mobile":{"background-color":"var(--ast-global-color-5)","background-image":"","background-repeat":"repeat","background-position":"center center","background-size":"auto","background-attachment":"scroll","background-type":"","background-media":"","overlay-type":"","overlay-color":"","overlay-opacity":"","overlay-gradient":""}},"footnotes":""},"categories":[7],"tags":[],"class_list":["post-1213","post","type-post","status-publish","format-standard","hentry","category-dotnet"],"_links":{"self":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/1213","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/types\/post"}],"replies":[{"embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/comments?post=1213"}],"version-history":[{"count":0,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/posts\/1213\/revisions"}],"wp:attachment":[{"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/media?parent=1213"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/categories?post=1213"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/rssfeedtelegrambot.bnaya.co.il\/index.php\/wp-json\/wp\/v2\/tags?post=1213"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}