<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Stefan Fachmann</title>
    <description>The latest articles on DEV Community by Stefan Fachmann (@it-fachbereich).</description>
    <link>https://dev.to/it-fachbereich</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3966389%2Fec45f3a2-88c8-43f7-bfad-f1c2fdc3569a.png</url>
      <title>DEV Community: Stefan Fachmann</title>
      <link>https://dev.to/it-fachbereich</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/it-fachbereich"/>
    <language>en</language>
    <item>
      <title>F# vs C# 3 — Conclusions</title>
      <dc:creator>Stefan Fachmann</dc:creator>
      <pubDate>Wed, 03 Jun 2026 15:56:59 +0000</pubDate>
      <link>https://dev.to/it-fachbereich/f-vs-c-3-conclusions-3jgl</link>
      <guid>https://dev.to/it-fachbereich/f-vs-c-3-conclusions-3jgl</guid>
      <description>&lt;p&gt;What can I say.&lt;/p&gt;

&lt;p&gt;Anyone claiming that F# is good mostly for finance and data processing and C# for everything else, has probably never written a single line of practical F# code.&lt;/p&gt;

&lt;p&gt;In previous two parts of the article, I tried to demonstrate that with F# you can achieve the same goals as with C#, but with less verbose, repetitive, structural code.&lt;/p&gt;

&lt;p&gt;How it started.&lt;/p&gt;

&lt;p&gt;At some point, developers realized that global state with unrestricted data access causes many side effects, producing insecure, error-prone, and hard-to-maintain code as software grows larger. That is when the idea emerged to bring data and the code operating on it together into a single unit, restricting direct access to the unit’s internal state and making software more secure and predictable. This is how data encapsulation was born.&lt;/p&gt;

&lt;p&gt;Alongside encapsulation, abstraction was introduced — the process of hiding how behavior works.&lt;/p&gt;

&lt;p&gt;Encapsulation (&lt;em&gt;hiding data&lt;/em&gt;) and abstraction (&lt;em&gt;hiding behavior&lt;/em&gt;) remain two foundational pillars of Object-Oriented Programming.&lt;/p&gt;

&lt;p&gt;And that is how OOP has worked ever since — developers bring data and behavior together (&lt;em&gt;classes&lt;/em&gt;) and define abstractions for them (&lt;em&gt;interfaces&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;For example, for C# developers — including myself — this has become a daily routine. And we rarely question it, because OOP languages like C# leave us little choice but to structure code this way.&lt;/p&gt;

&lt;p&gt;But if you ask yourself whether this repetitive routine is always necessary, the answer is — no.&lt;/p&gt;

&lt;p&gt;You don’t need OOP concepts to build stateless, streamlined request–response, data-processing pipelines, because in such systems there is no long-lived state to hide and protect. You have a request, and almost immediately you have a response. After that, everything is gone.&lt;/p&gt;

&lt;p&gt;That is what I tried to demonstrate in the first two parts of this article by applying FP concepts.&lt;/p&gt;

&lt;p&gt;And even if you have a classical desktop application, you don’t always need to approach it in an OOP way. Functional programming handles side effects not by hiding state, as encapsulation does, but by eliminating mutable state altogether.&lt;/p&gt;

&lt;p&gt;So, no mutable state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;no need for data encapsulation&lt;/li&gt;
&lt;li&gt;data is naturally tightened to the FP code operating on it&lt;/li&gt;
&lt;li&gt;no side effects — calling the same function with the same input always produces the same output&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ironically, these FP concepts existed before the rise of OOP, but they did not initially find their way into the mainstream. Only now, after many years, are they becoming more widely adopted.&lt;/p&gt;

&lt;p&gt;This explains the emergence of patterns such as &lt;em&gt;Functional Core, Imperative Shell&lt;/em&gt; and &lt;em&gt;functional-style OOP&lt;/em&gt;, which attempt to combine FP and OOP.&lt;/p&gt;

&lt;p&gt;Just think of the amount of FP features Microsoft added to C# to make it more FP like — delegates, lambda expressions, LINQ, expression-bodied members, init-only setters and records (with unions to come next) to mimic immutability, advanced pattern matching, etc.&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generic delegates with &lt;code&gt;Func&lt;/code&gt; and &lt;code&gt;Action&lt;/code&gt; combined with lambda expressions are verbose, boilerplate-heavy and often hard to read and write
LINQ is another attempt to introduce FP concepts into C#, but it is still relatively verbose&lt;/li&gt;
&lt;li&gt;Expression-bodied members and init-only setters are attempts to introduce immutability, but they remain verbose and often boilerplate-heavy&lt;/li&gt;
&lt;li&gt;Tuples, records, and upcoming unions are attempts to introduce algebraic types. In case of record type, it is just syntactic sugar compiled as a class under the hood, automatically generating init-only properties for you, making positional records strictly immutable by default. Yet, if you add a new non-positional property to it, without initializing it, the compiler won’t complain and your application will potentially crash with a &lt;code&gt;NullReferenceException&lt;/code&gt;. Not so with true algebraic data, with compiler strictly enforcing that all data pieces exist and are fully initialized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where F#, a multi-paradigm but functional-first language, comes into play.&lt;/p&gt;

&lt;p&gt;By multi-paradigm, I mean you can use both FP and OOP.&lt;/p&gt;

&lt;p&gt;However, functions are the core of F#. Defining a function is as simple as: let &lt;code&gt;add a b = a + b&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In F#, immutability and strong type checking come naturally.&lt;/p&gt;

&lt;p&gt;So again, F# is FP-first. That is its primary purpose, and you should stick to it. Don’t misuse it to fall back into old OOP habits. You can still use OOP when necessary, mainly for interoperability with .NET or for grouping related functionality.&lt;/p&gt;

&lt;p&gt;F# is business-logic-first, with structural code pushed to the background. When used properly, you can often avoid structural overhead altogether, as shown in the first two parts of this article.&lt;/p&gt;

&lt;p&gt;I mentioned earlier that Copilot can generate repetitive and verbose code. What I didn’t know at the time is that, starting June 1, 2026, Copilot is moving toward usage-based billing based on token consumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  More verbose and boilerplate-heavy code means more syntactic overhead. More syntactic overhead means lower token efficiency. And lower token efficiency means higher AI token consumption.
&lt;/h2&gt;

&lt;p&gt;When comparing both solutions, it becomes clear that F# is significantly more token-efficient than C#, thanks to its functional nature, concise syntax, and strong type system with type inference.&lt;/p&gt;

&lt;p&gt;So, with all the examples and conclusions in mind, I still wonder why Microsoft treats F# like a stepchild. Why is it still a niche language? Why isn’t it promoted more?&lt;/p&gt;

&lt;p&gt;It is also up to us developers to promote F# by using it in real-world applications. The more people use it, the more visible and accepted it becomes.&lt;/p&gt;

&lt;p&gt;Of course, moving from OOP to FP is not trivial. It requires a shift in thinking. But there are many excellent resources available.&lt;/p&gt;

&lt;p&gt;One of the most useful is &lt;a href="https://fsharpforfunandprofit.com/" rel="noopener noreferrer"&gt;F# for Fun and Profit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If I had to suggest a starting path, I would recommend:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/series/why-use-fsharp/" rel="noopener noreferrer"&gt;Why use F#?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/series/why-use-fsharp/" rel="noopener noreferrer"&gt;Thinking functionally&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fsharpforfunandprofit.com/series/understanding-fsharp-types/" rel="noopener noreferrer"&gt;Understanding F# types&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I hope this article helps you better understand F#.&lt;/p&gt;

&lt;p&gt;So instead of asking whether it’s worth learning F#, just give it a try — as I did.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>dotnetcore</category>
    </item>
    <item>
      <title>F# vs C# — Token Efficiency</title>
      <dc:creator>Stefan Fachmann</dc:creator>
      <pubDate>Wed, 03 Jun 2026 15:56:41 +0000</pubDate>
      <link>https://dev.to/it-fachbereich/f-vs-c-token-efficiency-5h4d</link>
      <guid>https://dev.to/it-fachbereich/f-vs-c-token-efficiency-5h4d</guid>
      <description>&lt;p&gt;In this second part of the article, we will discuss the changes made to the shopping app by adding two more subdomains to make the web application more realistic — Customer and Order.&lt;/p&gt;

&lt;p&gt;In our shopping application, we have products, and we have customers who order these products.&lt;br&gt;
Of course, a shopping domain consists of many more subdomains.&lt;/p&gt;

&lt;p&gt;However, our focus will be limited to codebase growth and code complexity when introducing new functionality or new subdomains.&lt;/p&gt;

&lt;p&gt;Here, we focus on &lt;strong&gt;token efficiency&lt;/strong&gt; — how effectively a programming language can produce high‑quality, maintainable software using the fewest syntactic tokens (symbols, keywords, punctuation), thereby reducing syntactic overhead.&lt;/p&gt;

&lt;p&gt;By &lt;strong&gt;syntactic overhead&lt;/strong&gt;, I primarily mean the ceremonial and verbose code that adds structure but contributes little to the actual business logic.&lt;/p&gt;

&lt;p&gt;After adding the Customer and Order subdomains to our C# shopping application, we significantly increased the syntactic overhead.&lt;/p&gt;

&lt;p&gt;By using a typical C# &lt;em&gt;“Ports and Adapters”&lt;/em&gt; style, a lot of structural code was added.&lt;/p&gt;

&lt;p&gt;It becomes a highly &lt;strong&gt;ceremonial process&lt;/strong&gt;, where you must declare an interface (&lt;em&gt;port&lt;/em&gt;) and then provide an implementation (&lt;em&gt;adapter&lt;/em&gt;) for it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Program to an interface, not to an implementation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Just look at the diagram below to see how complex our project has become and compare it with the equivalent diagram from part 1.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn9l2oa03ny8y9tphjcv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmn9l2oa03ny8y9tphjcv.png" alt=" " width="800" height="1273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code changes are &lt;a href="https://github.com/it-fach-bereich/Shopping.CSharp.Api/tree/added-customer-and-order-subdomains" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We won’t discuss implementation details, because there is nothing particularly special about them — except for placing an order, which includes more complex business logic.&lt;/p&gt;

&lt;p&gt;The main focus is the significant increase in structural code (interface/implementation definitions, dependency injection, etc.) — code that does not directly contribute to business logic.&lt;/p&gt;

&lt;p&gt;And the more subdomains we add, the more this structural code grows.&lt;/p&gt;

&lt;p&gt;I know Copilot can generate such repetitive code for you.&lt;br&gt;
But the question is — do we really need to do it in an OOP way?&lt;/p&gt;

&lt;p&gt;With F# — no.&lt;/p&gt;

&lt;p&gt;No, because in F#, each function signature effectively acts as an interface.&lt;br&gt;
Any function can be replaced by another function with the same signature, often in combination with partial application and module naming.&lt;/p&gt;

&lt;p&gt;So, if we want to swap a Cosmos DB implementation for a MongoDB one, we proceed in a similar way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;create a MongoDB implementation (similar to the Cosmos DB version)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;define an abstraction via function composition and partial application&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;keep the same function signature and module structure&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we simply replace the reference from the Cosmos DB project to the MongoDB one — without modifying any domain logic in &lt;code&gt;Shopping.Products.Domain&lt;/code&gt; module.&lt;/p&gt;

&lt;p&gt;So, what in C# is achieved through &lt;em&gt;“Ports and Adapters”&lt;/em&gt; using interfaces and implementations, in F# is achieved through function signatures, partial application, and module organization.&lt;/p&gt;

&lt;p&gt;Now back to the F# solution.&lt;/p&gt;

&lt;p&gt;I’ve added Customer and Order subdomains and performed some refactoring.&lt;/p&gt;

&lt;p&gt;The code changes are &lt;a href="https://github.com/it-fach-bereich/Shopping.FSharp.Api/tree/added-customer-and-order-subdomains" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the Product repository, I replaced the generic &lt;code&gt;‘T&lt;/code&gt; with the specific &lt;code&gt;Product&lt;/code&gt; type, which should have been done from the beginning.&lt;/p&gt;

&lt;p&gt;However, this resulted in the following error:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Value restriction: The value ‘getById’ has an inferred generic function type val getById: (string -&amp;gt; ‘_a -&amp;gt; Threading.Tasks.Task&amp;lt;Shopping.Common.Types.Result&amp;lt;Product,Shopping.Common.Types.ReadItemFailureReason&amp;gt;&amp;gt;)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;getById&lt;/code&gt; isn’t a function but an identifier bound to a function of type (&lt;code&gt;string -&amp;gt; ‘_a -&amp;gt; Threading.Tasks.Task&amp;lt;Shopping.Common.Types.Result&amp;lt;Product,Shopping.Common.Types.ReadItemFailureReason&amp;gt;&amp;gt;)&lt;/code&gt;, because &lt;code&gt;getByIdAsync&amp;lt;Product&amp;gt; (getContainer ())&lt;/code&gt; call, returns a function waiting for missing parameters. A typical case of partial application in F#.&lt;/p&gt;

&lt;p&gt;The error was caused by the unresolved generic &lt;code&gt;‘a&lt;/code&gt; parameter propagating upward through the entire call chain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;box&lt;/code&gt; based &lt;code&gt;getPartitionKey&lt;/code&gt; chain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;box accepts 'a
  → getPartitionKey: 'a -&amp;gt; PartitionKey
      → getByIdAsync&amp;lt;Product&amp;gt;: Container -&amp;gt; string -&amp;gt; 'a -&amp;gt; Task&amp;lt;Result&amp;lt;Product,...&amp;gt;&amp;gt;
          → getByIdAsync&amp;lt;Product&amp;gt; (getContainer ()): string -&amp;gt; 'a -&amp;gt; Task&amp;lt;...&amp;gt;
              → let getById = getByIdAsync&amp;lt;Product&amp;gt; (getContainer ())
                              ← partial application, 'a still unresolved → VALUE RESTRICTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We could fix it using explicit type annotation of &lt;code&gt;string -&amp;gt; obj -&amp;gt; Task&amp;lt;Result&amp;lt;Product,ReadItemFailureReason&amp;gt;&amp;gt;&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;But avoid any type annotations and use type inference instead.&lt;/p&gt;

&lt;p&gt;Type inference in F# means the compiler automatically figures out the types of values and functions from how you use them, so you often don’t need to write explicit type annotations. With type inference come such advantages like faster coding, better readability, etc. But most importantly easier refactoring, in case of type changes of for example input parameters.&lt;/p&gt;

&lt;p&gt;To resolve this cleanly, I introduced a discriminated union&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight fsharp"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="nc"&gt;CosmosPartitionKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;StringKey&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nc"&gt;IntKey&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;so that all types can be resolved at compile time and partial application remains safe across the entire call chain.&lt;/p&gt;

&lt;p&gt;Advantages of this approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type safety at compile time — the DU makes invalid partition key types impossible to represent. With &lt;code&gt;box&lt;/code&gt;, passing any type compiles fine and only fails at runtime with &lt;code&gt;NotSupportedException&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;No runtime type checking — The &lt;code&gt;box&lt;/code&gt; version uses &lt;code&gt;:?&lt;/code&gt; type tests — runtime reflection. The DU version is a straight pattern match, resolved entirely at compile time.&lt;/li&gt;
&lt;li&gt;Exhaustive checking — The compiler warns you if a new DU case is added (e.g. &lt;code&gt;BoolKey&lt;/code&gt;) but &lt;code&gt;getPartitionKey&lt;/code&gt; is not updated. With &lt;code&gt;box&lt;/code&gt;, you’d silently hit the &lt;code&gt;raise&lt;/code&gt; branch at runtime.&lt;/li&gt;
&lt;li&gt;Eliminates the generic leak — As discussed, the DU gives &lt;code&gt;getPartitionKey&lt;/code&gt;a concrete signature, making the entire call chain safe for partial application.&lt;/li&gt;
&lt;li&gt;Removes the defensive &lt;code&gt;raise&lt;/code&gt; — The box version needs a catch-all | _ -&amp;gt; raise(NotSupportedException(…)) because any type could be passed. The DU version has no impossible cases to guard against.&lt;/li&gt;
&lt;li&gt;Self-documenting — The type &lt;code&gt;CosmosPartitionKey&lt;/code&gt; in the signature immediately communicates the intent and valid inputs — no need to read the implementation to understand what is accepted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;DU version moves all validation from runtime to compile time, thus eliminating an entire class of bugs, especially runtime bugs.&lt;/p&gt;

&lt;p&gt;Below is how DU based &lt;code&gt;getPartitionKey&lt;/code&gt; chain looks now:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getPartitionKey: CosmosPartitionKey -&amp;gt; PartitionKey   ← concrete, no 'a
  → getByIdAsync&amp;lt;Product&amp;gt;: Container -&amp;gt; string -&amp;gt; CosmosPartitionKey -&amp;gt; Task&amp;lt;Result&amp;lt;Product,...&amp;gt;&amp;gt;
      → getByIdAsync&amp;lt;Product&amp;gt; (getContainer ()): string -&amp;gt; CosmosPartitionKey -&amp;gt; Task&amp;lt;...&amp;gt;
          → let getById = getByIdAsync&amp;lt;Product&amp;gt; (getContainer ())
                          ← safe, CosmosPartitionKey is concrete → NO VALUE RESTRICTION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;Make illegal states unrepresentable.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is &lt;strong&gt;type-driven development&lt;/strong&gt; — you model &lt;em&gt;what exists&lt;/em&gt; and &lt;em&gt;what is valid&lt;/em&gt; in your domain as types, and the compiler enforces correctness throughout. The correctness of your business logic is therefore guided and enforced by the model design.&lt;/p&gt;

&lt;p&gt;In C# type-driven development is also possible. But is very verbose because of the type system. Which in newer versions of C# tries to mimic F#’s type system by introducing records, pattern matching and recently discriminated unions. But all this are just syntactic sugar, which has little to do with algebraic types or real power of F#’s pattern matching.&lt;/p&gt;

&lt;p&gt;Now let’s compare the number of projects in C# and F#. &lt;/p&gt;
&lt;h2&gt;
  
  
  We have 20 in C# against 8 in F#.
&lt;/h2&gt;

&lt;p&gt;There is a significant increase in C# projects and files, because in C# we need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;separate projects per layer/concern (interfaces, implementations, adapters) to enforce dependency direction&lt;/li&gt;
&lt;li&gt;explicit separation of abstraction and implementation — every interface needs a concrete class separately declared and registered&lt;/li&gt;
&lt;li&gt;explicit class/interface declarations for every abstraction (no structural/implicit typing)&lt;/li&gt;
&lt;li&gt;dedicated files per type (one class/interface per file convention)&lt;/li&gt;
&lt;li&gt;DI wiring boilerplate in &lt;code&gt;Program.cs&lt;/code&gt; binding interfaces to implementations&lt;/li&gt;
&lt;li&gt;DTOs + mappers per domain as separate types and files&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The number of files in C# solution has also significantly grown, because C# (and OOP in general) encourages separation of concerns where each file/class a single responsibility.&lt;/p&gt;

&lt;p&gt;Idiomatic F# does not force &lt;strong&gt;abstraction-to-implementation&lt;/strong&gt; separation as a structural requirement. Interfaces and classes are mostly used for .NET OO interoperability,&lt;/p&gt;

&lt;p&gt;Below are C# and F# Order services for comparison, as most complex service for now.&lt;/p&gt;

&lt;p&gt;C#&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;F#&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Notice, in F# version there are no abstraction-to-implementation separation, class and constructor declarations, and no DI. As with Product repository, we abstract/hide the implementation in Customer and Order repositories by again using partial application.&lt;/p&gt;

&lt;p&gt;So, without abstraction-to-implementation separation, there is no need for separate projects per layer/concern with additional explicit separation of abstraction and implementation and no need for DI. Therefore, there is less structural syntactic overhead. Therefore F# code is more token efficient.&lt;/p&gt;

&lt;p&gt;Take a look at the place order business logic. Notice how elegant recursion, pattern matching, and piping make the F# implementation. Conversely, the C# implementation looks cluttered with sequential loops, conditional checks, and exception handling.&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Program.fs&lt;/code&gt; we still have initially scaffolded starter F# code.&lt;/p&gt;

&lt;p&gt;Unlike &lt;code&gt;Program.cs&lt;/code&gt; which contains a lot of changes because of two new subdomains.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxah83m0r66amlhvie07e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxah83m0r66amlhvie07e.png" alt=" " width="493" height="828"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is a new detailed diagram of the web API with all its dependencies, after adding the two new subdomains. Compare the previous version in part 1 of the article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mue3hempsfjrywha7ru.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4mue3hempsfjrywha7ru.png" alt=" " width="800" height="830"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once again, dependencies are module-level with direct project references. The domain orchestrates calls to repository modules rather than interface implementations. There is no need for structural code.&lt;/p&gt;

&lt;p&gt;There is also the reason why we have less files in our F# solution. Because F# is based on modules, where we can have separate modules for data and modules for functions operating on that data. Or we can have modules which include data and functions operating on that data. We can still separate concerns, but we do so at module-level.&lt;/p&gt;

&lt;p&gt;Also compare high-level diagrams with the second one reflecting domain changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3sturdj2ebr7shku1ct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3sturdj2ebr7shku1ct.png" alt=" " width="799" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9h5fpktgt3jkkb7mtgn7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9h5fpktgt3jkkb7mtgn7.png" alt=" " width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;High-level diagram after Customer and Order subdomains were added&lt;br&gt;
Even after adding two new domain layers, Customer and Order, all three domain layers still reference repository projects directly and use module functions as contracts (no explicit .NET interface projects).&lt;/p&gt;

&lt;p&gt;Now you can clearly see:&lt;/p&gt;

&lt;h2&gt;
  
  
  With less F# code, you can achieve the same result that requires significantly more structural and repetitive syntax in C#.
&lt;/h2&gt;

&lt;p&gt;That is exactly what &lt;strong&gt;token efficiency&lt;/strong&gt; in a programming language means.&lt;/p&gt;

&lt;p&gt;And this isn’t the final conclusion.&lt;/p&gt;

&lt;p&gt;In part 3, I present more opinionated conclusions about F#.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>dotnetcore</category>
    </item>
    <item>
      <title>F# vs C#</title>
      <dc:creator>Stefan Fachmann</dc:creator>
      <pubDate>Wed, 03 Jun 2026 15:56:22 +0000</pubDate>
      <link>https://dev.to/it-fachbereich/f-vs-c-cn</link>
      <guid>https://dev.to/it-fachbereich/f-vs-c-cn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faadvh0pzi8vmk1lzu6k5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faadvh0pzi8vmk1lzu6k5.png" alt="The picture depicts a standoff between an overwhelming C# army, dominant in spread and use, and a smaller but much more effective F# army." width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This article is dedicated to F# language, more precisely how it is compared to C#.&lt;/p&gt;

&lt;p&gt;Writing this article was motivated by my love for functional programming, and particularly for F#. My first contact with F# happened more than 10 years ago. Since then, I periodically came back to do something with F#. Mostly reading new F# documentation and articles dedicated to F#, doing small F# projects, and reading endless "Is F# worth learning" questions on Reddit, Quora and other sources.&lt;/p&gt;

&lt;p&gt;At some point I got pissed off by all this, especially of "worth learning" questions, and decided to give F# a real try. So, I picked up one of our C# projects (.Net Web API) and started reimplementing it in F#.&lt;/p&gt;

&lt;p&gt;That is when I really realized how functional programming style and F# type system was superior to C# OOP and its type system. Nothing against C#. I consider it to be one of the best object-oriented languages.&lt;/p&gt;

&lt;p&gt;And C# also has some functional features. But they seem like foreign bodies unnaturally glued to C# OOP style.&lt;/p&gt;

&lt;p&gt;Below is a simple square operation written in both F# and C#, to prove the point.&lt;/p&gt;

&lt;p&gt;F#&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ocaml"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;square&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;and here is C# equivalent&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MathHelpers&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;
  &lt;span class="err"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;dynamic&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;While it takes eight lines of code to declare a simple square operation in C#, F# achieves the same result in just one. By leveraging automatic type inference, F# removes the need for explicit type declarations and generic math constraints. You don't have to cast generics to dynamic variables or wrap everything in a class - you simply write the logic and let the compiler handle the ceremony.&lt;/p&gt;

&lt;p&gt;Why do we need in C# a class definition to be able to define a simple math operation? why do we need this boilerplate code?&lt;/p&gt;

&lt;p&gt;So, with those questions in mind, back to my Web API project.&lt;/p&gt;

&lt;p&gt;It is a .Net Web API three-layer microservice hosted by Azure, doing CRUD and searching operations in a Cosmos DB, with relatively large code base.&lt;/p&gt;

&lt;p&gt;So, I just wrote a simple typical .Net Web API doing Cosmos DB and Blob Storage CRUD operations - with an exception filter, DTOs, business models and some mappers. It is a functioning web app with not much business logic. The intent was to just prove my point.&lt;/p&gt;

&lt;p&gt;The code can be found &lt;a href="https://github.com/it-fach-bereich/Shopping.CSharp.Api/tree/CSharpVsFSharp1.0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here is a detailed diagram of the Web API with all its dependencies.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcqr7c0jkzd6ql9rx826.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmcqr7c0jkzd6ql9rx826.png" alt=" " width="800" height="957"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you see, each implementation is decoupled via an abstraction. You know&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Program to an interface, not to an implementation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So, the question again - do we always need the complexity of defining abstractions and their implementations? And do we always need this ceremonial interface and class definitions, with repetitive dependency injection, to execute a piece of code? Particularly in this concrete example. And particularly when using scoped and transient services.&lt;/p&gt;

&lt;p&gt;Is it worth defining interfaces and class implementations for services which live a couple of milliseconds or even seconds. A scoped service's life begins when the request starts and ends when the response is sent. Transient services have no scope - they're created every time they're resolved, even multiple times during the same request.&lt;/p&gt;

&lt;p&gt;I know Copilot Agent can generate code for you. The real question is - do we need to define complex objects with dozens of methods if we only call one or two of them per request. One of the main purposes of OOP is encapsulation of data and methods which work on that data. And probably makes sense for long living objects, for example in a desktop application, when using for example UI components.&lt;/p&gt;

&lt;p&gt;We have singleton services in .Net web applications. But they must be thread safe and relatively lightweight.&lt;/p&gt;

&lt;p&gt;This is where F# comes into play.&lt;/p&gt;

&lt;p&gt;I don't intend to make an introduction to F# here or to explain any of FP or F# concepts in detail. There are many F# dedicated resources out there and one of them is &lt;a href="https://fsharpforfunandprofit.com/" rel="noopener noreferrer"&gt;F# for fun and profit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I just want to show how much easier and faster it is to do the same stuff in F# without OOP corsets of C#.&lt;/p&gt;

&lt;p&gt;The code can be found &lt;a href="https://github.com/it-fach-bereich/Shopping.FSharp.Api/tree/FSharpVsCSharp1.0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And here is the detailed diagram of the web API with all its dependencies, and much less complexity compared to the C# project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvgh390714wkfxx1zckh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmvgh390714wkfxx1zckh.png" alt=" " width="800" height="446"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you notice, there are only module-level dependencies. The domain logic calls repository modules directly, rather than calling interface implementations.&lt;/p&gt;

&lt;p&gt;First, I want to compare &lt;code&gt;Program.cs&lt;/code&gt; with &lt;code&gt;Program.fs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In &lt;code&gt;Program.cs&lt;/code&gt; we register a lot of services with the built-in DI container. Including our own services and their corresponding implementations to be able to inject them later in our constructors.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;Program.fs&lt;/code&gt; we still have initially scaffolded starter F# code when selecting a Web API template, and no service registrations. Explanation later in the article.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;Second, let's compare the number of projects in C# and F#. We have 8 in C# against 6 in F#. And the reason for that is that I have implementations and their abstractions in one place. So, there are no separate class libraries for abstractions, and their implementations like in C#.&lt;/p&gt;

&lt;p&gt;Let's look at the implementation for our Blob Storage files repository:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And below I have the abstraction:&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And what is interesting here is the latter.&lt;/p&gt;

&lt;p&gt;I abstract the implementation away by using partial application, a functional programming technique where we take a function with multiple arguments and pre-fill some of them, producing a new function that takes the remaining arguments.&lt;/p&gt;

&lt;p&gt;So pre-filled here is &lt;code&gt;BlobServiceClient&lt;/code&gt; service, the specific implementation of Blob Storage repository. And with the help of partial application, we can abstract the implementation away by hiding the implementation details, as I do here with &lt;code&gt;BlobServiceClient&lt;/code&gt; which is used to access Azure Blob Storage.&lt;/p&gt;

&lt;p&gt;So, now I just need to open the &lt;code&gt;Shopping.Files.Repository.Product&lt;/code&gt; module, under our &lt;code&gt;Shopping.Products.Domain&lt;/code&gt; module and voila you can call the abstraction.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;There are no implementation details when calling the abstraction, because it is hidden by partial application. And it is clear what implementation will be called because of the opened full module path, which provides the necessary context. And there is no constructor with dependency injection because there is nothing to inject. Therefore, no service registrations with the built-in DI container are necessary -for our services.&lt;/p&gt;

&lt;p&gt;I still use a built-in DI container in our Blob Storage repository project, to create a &lt;code&gt;BlobServiceClient&lt;/code&gt; singleton and provide it when necessary.&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;In a typical ASP.NET Core app the DI container is built once at startup by the host builder and reused. Here, because this module is standalone, the lazy ensures the same behavior - deferred construction and single instance - without relying on the web host. If we were already inside the web app's startup, we'd normally register &lt;code&gt;BlobServiceClient&lt;/code&gt; in &lt;code&gt;ConfigureServices&lt;/code&gt; and resolve it via the host's provider; the &lt;code&gt;lazy&lt;/code&gt; pattern is mainly useful when we're outside that lifecycle (e.g., library, script, or manual invocation).&lt;/p&gt;

&lt;p&gt;I could of course just create an instance of &lt;code&gt;BlobServiceClient&lt;/code&gt; and provide the cached instance, because of &lt;code&gt;lazy&lt;/code&gt; block, when necessary. I use the built-in ID container with generic capabilities in case I want to register more services. Anyway, the creation of &lt;code&gt;BlobServiceClient&lt;/code&gt; is encapsulated in the repository itself for further use, thus completely decoupling it from Web API project.&lt;br&gt;
Now compare this with code overhead needed to achieve the same result in C#.&lt;/p&gt;

&lt;p&gt;We have file repository abstraction&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;We have the implementation, with a base class&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;And of course we have to inject then &lt;code&gt;IProductFileRepository&lt;/code&gt; dependency in order to use it&lt;/p&gt;


&lt;div class="ltag_gist-liquid-tag"&gt;
  
&lt;/div&gt;


&lt;p&gt;C# adds a lot of &lt;strong&gt;syntactic overhead&lt;/strong&gt; - with all the abstraction and implementation definitions and the required dependency‑injection setup - compared to F#, even for simple file‑related operations.&lt;/p&gt;

&lt;p&gt;That's all for now.&lt;/p&gt;

&lt;p&gt;In part 2 we will extend the example with Customer and Order business domain and new implementations, and and talk about &lt;strong&gt;token efficiency&lt;/strong&gt;.&lt;/p&gt;

</description>
      <category>fsharp</category>
      <category>csharp</category>
      <category>dotnet</category>
      <category>dotnetcore</category>
    </item>
  </channel>
</rss>
