The combined power of F# and C#

8/2/2023

Where C# is the most dominant language in the .NET world, other languages are built on top of the Framework that deserves their respective place. F# is strong when it comes down to functional programming! In this blog post, we will leverage the power of F# and C# to showcase where both excel!

Why does it work?

Before I go into the details of the code, I want to explain why this works. Why can we use F# code in C# and vice versa? The answer is simple: the .NET Framework is a runtime that can execute code written in different languages. The runtime is responsible for compiling the code to an intermediate language (IL) that the runtime can execute. If your code is dependent on other libraries, you are not relying on the source code but the IL code of that library. I already had a whole blog post about that: "What is the difference between C#, .NET, IL and JIT?". Have a read here if you are interested in more details.

.NET

F#

Here is a very brief introduction: F# is a functional-first programming language in the .NET ecosystem. It is designed to provide the expressive power of functional programming alongside the robustness of object-oriented paradigms. It enjoys all of the benefits of the .NET Framework itself: open-source, platform-independent, and strongly typed.

let rec factorial n =
    if n = 0 then 1 else n * factorial (n - 1)

printfn "Factorial of 5 is %d" (factorial 5)

F# also has some distinct features C# does not offer, the most prominent: Discriminated unions.

The use case

I want to showcase where F# excels - and oftentimes, that can be the domain layer of your application. Especially then when you have a lot of calculations (basically where functional programming excels in the first place). Let's create a small domain project that is written in F# and see how it could look like in C#. I will create a "small online" shop that can take orders and calculate a discount (a percentage or a fixed amount). Before we go into F#, have a look at the C# code:


public class Product 
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

public class OrderLine 
{
    public Product Product { get; set; }
    public int Quantity { get; set; }
}

public enum DiscountType { Percentage, FixedAmount }

public class Discount 
{
    public DiscountType Type { get; set; }
    public decimal Value { get; set; }
}

public record Order(List<OrderLine> OrderLines, Discount Discount)
{
    public decimal CalculateTotalPrice() 
    {
        decimal subtotal = OrderLines.Sum(ol => ol.Product.Price * ol.Quantity);
        decimal totalDiscount = 0M;

        if (Discount != null) {
            totalDiscount = Discount.Type == DiscountType.Percentage 
                                ? subtotal * Discount.Value / 100M 
                                : Discount.Value;
        }

        return subtotal - totalDiscount;
    }
}

Now for the sake of simplicity, I left out a lot of validation and error handling. I hope you get the idea anyway. Now let's see how this would look like in F#:

type Product = { Name: string; Price: decimal }
type OrderLine = { Product: Product; Quantity: int }

type Discount = 
    | Percentage of float
    | FixedAmount of decimal

type Order(orderLines: OrderLine list, discount: Discount option) =
    member this.CalculateTotalPrice() =
        let subtotal = List.sumBy (fun ol -> ol.Product.Price * (decimal ol.Quantity)) orderLines
        let totalDiscount =
            discount
            |> Option.map (fun d -> 
                match d with
                | Percentage p -> subtotal * (decimal p / 100M)
                | FixedAmount f -> f)
            |> Option.defaultValue 0M
        subtotal - totalDiscount

Wow - that is really slim. Let's go through the code step by step. First, we have the Product and OrderLine types. They are just simple records that hold the data. Then we have the Discount type. This is a discriminated union that can either be a Percentage or a FixedAmount. The Order type class takes a list of OrderLine and an optional Discount. The CalculateTotalPrice method is the same as in the C# example. The only difference is that we use the Option type to handle the null case. The Option type is a discriminated union that can either be Some or None. We can use pattern matching to handle both cases. In this case we use the Option.map function to map the Discount to a decimal value. If the Discount is None we use the Option.defaultValue function to return 0M.

Now we can easily integrate that to a controller:

[ApiController]
[Route("api/orders")]
public class OrderController : ControllerBase
{
    private static readonly List<Order> Orders = new();

    [HttpPost]
    public ActionResult<Order> CreateOrder([FromBody] List<OrderLine> orderLines, [FromBody] Discount discount)
    {
        var fsharpOrderLines = ListModule.OfSeq(orderLines);
        var order = new Order(fsharpOrderLines, discount);
        Orders.Add(order);
        return CreatedAtAction(nameof(GetOrderById), new { id = Orders.Count - 1 }, order);
    }

    [HttpGet("{id:int}")]
    public ActionResult<Order> GetOrderById(int id)
    {
        if (id < 0 || id >= Orders.Count)
        {
            return NotFound();
        }

        return Orders[id];
    }

    [HttpGet("{id:int}/total")]
    public ActionResult<decimal> GetOrderTotalPrice(int id)
    {
        if (id < 0 || id >= Orders.Count)
        {
            return NotFound();
        }

        return Orders[id].CalculateTotalPrice();
    }

    [HttpGet]
    public ActionResult<IEnumerable<Order>> GetAllOrders()
    {
        return Orders;
    }
}

There might be a very special line here that needs some clarification:

var fsharpOrderLines = ListModule.OfSeq(orderLines);

The ListModule.OfSeq function is a helper function that converts a List<T> to a seq<T>. The seq<T> type is a sequence of elements. It is a lazy collection that is only evaluated when needed. This is a very powerful concept that is used a lot in F#. You can read more about it here. In this case we need to convert the List<T> to a seq<T> because the Order type expects a seq<T> as input.

Conclusion

I hope this blog post gave you a good overview of how you can use F# and C# together. It can be a very powerful tool, and F# has some unique strengths that can make your life easier!

Resources

  • Source code to this blog post: here
  • All my sample code is hosted in this repository: here

LINQ: Select.Where or Where.Select?

LINQ is a very powerful tool for querying data. As the majority of functions are built on top of IEnumerable<T> and it, in most cases returns IEnumerable<T> as well, it is very easy to chain multiple functions together. That leaves you with a question: which one should I use, Select.Where or Where.Select?

Wrap Event based functions into awaitable Tasks - Meet TaskCompletionSource

You might have code where an object offers you an event to notify you when a specific operation is done. But event's can be tricky to use, especially when you want to have a continuous flow in your application.

That is where TaskCompletionSource comes into play. We can "transform" an event based function into something which is await-able from the outside world via the await keyword.

Introduction to WebApplicationFactory

This article will show you what exactly a WebApplicationFactory is and why it is so "mighty" when it comes down to testing. I will highlight some of the properties, which you can leverage to write powerful tests.

An error has occurred. This application may no longer respond until reloaded. Reload x