Make F# play nice with C# - how to compose dependencies while hosting with C# project

Jun 14, 2020 min read

F# is a nice functional alternative in .NET. I have convinced my teammates to use F# in our project at my work in a small new accounting bounded context that would be hosted by .NET Core C# host and Autofac based composition root. We already have a small F# based azure-function which has been a warm-welcomed area to extend/introduce changes so why not take F# to the next level? If you are looking for some hints how to deal with F# and C# in one solution this is a must-read.

Introduction

Code samples with EventDispatcher and CompositionRoot will use Autofac but the presented techniques should be easily applicable to Microsoft IoC Container and MediatR. Samples holds some mysterius _fSqlConnectionFactory but let me write about easy Dapper Wrapper in F# and Option/Nullable interop in next post.

Motivation

If F# is .Net and if C# is .Net then what possibly could go wrong? People are saying that having C# and F# is easy… but it cuts both ways. If you don’t mind having:

  • Classes in F#
  • Interface implementations in F#
  • C# Tasks in F#
  • FSharpAsync in C#
  • FSharpFunc in C#
  • FSharpTuple in C# then yes, this is easy. But I didn’t want to start doing this…this… heresy? The first version of my F# and C# integration was like this: the domain layer looked like in F#-only solution, but the application layer was not so cool… After staring at this code for a few hours:
1    type AccountingEventHandler(io: PrepareReportLineFlow.IO) = // class in F# :( DI using ctor -1
2        interface Events.EventHandler<GlobalEvents.BillingItemsArchived> with // interface implementation ;/
3            member this.Handle(event) = // interface implementation 
4                let invoiceId = event.InvoiceId |> InvoiceId
5                PrepareReportLineFlow.prepareReportLine io invoiceId |> Async.RunSynchronously

the decision has been made - put additional effort to allow F# code to be more functional-first.

Background

To give you some background the solution hosts an API with 4 bounded contexts. There is an event which is being emitted whenever billing items are archived (so invoice was issued). For event handling, we use IoC Container. It simply looks for classes implementing EventHandler<T> interface and executes their Handle(TEvent) method. Composition root and bounded context are hosted by .Net Core API. More or less like this:

I hope you get the idea. Don’t bother with domain meaning here - I will focus on technical details related to F# and C# integration.

Dealing with dependencies in F# (skip it if you know the partial application, IO Sandwich)

So let me write a few words about dealing with dependencies in F#. Partial application is a simple yet effective technique to compose dependencies in a functional style. It’s nothing more than passing functions as parameters to other functions (so called higher order functions) to return new function. It’s easy with currying. Imagine that you want to

  1. read specific product from the database
  2. apply coupon code to product
  3. save the discounted product for after-marketing.
  4. return the discounted product
  5. Note that coupon codes cannot stack

In OO probably you would use repositories, dependency injection, interfaces to make this work (or write an ugly transaction-script). In F# we do IO Sandwich (but you can write ugly transaction-script here as well). Having in mind that in the application layer we want to design use-cases using domain, ports & adapters. This approach allows us to prevent async-injection (read more on ploeh blog [1]). The application layer could look like this;

 1module ApplyCouponCodeFlow
 2    type IO = {
 3       ReadProductBy: ProductId -> Async<Product>
 4       ReadCouponBy: CouponCode -> Async<Coupon>
 5       SaveDiscountedProduct: DiscountedProduct -> Async<Unit>
 6    }
 7
 8    let applyCouponCode io (productId, couponCode) =
 9        async {
10            let! product = io.ReadProductBy productId // IO
11            let! coupon = io.ReadCouponBy couponCode // IO
12            let discountedProduct = product |> Salesman.Apply discount // Domain Operation - pure function, no side-effects including async.
13            do! io.SaveDiscountedProduct // IO
14            return product
15        }

If you checked Ploeh blog [2] that I mentioned you saw that Mark moves this even more to the “edges” to .Net API Controllers. I like to have it in the application layer. I think that API is just a driving port and shouldn’t be responsible for the application control flow despite the name “Controller” because these particular controllers are coupled with selected hosting technology: .Net core, web API with OWIN, etc and controls the flow of http requests [3]. What is cool and worth to emphasizing is that the code is super-testable. You will mention my words once you will give it a try. Having such a module with the Use-Case we can compose the functions;

1// In your composition root:
2let ComposeApplyCouponFlow =
3    let io = {
4        ReadProductBy = // DatabaseAdapter.ReadProductBy
5        ReadCouponBy = // DatabaseAdapter.ReadCouponCode
6        SaveDiscountedProduct = // DatabaseAdapterSpecificForPostmarketing.SaveDiscountedProduct
7    }
8    ApplyCouponCodeFlow.applyCouponCode io

I would consider skipping creating IO type for use cases with one or two side functions, however I really like the idea of code being explicit about side-effect functions. Finally here is a quick, easy example of usage in Giraffe:

1let webApp =
2    choose [
3        routef "/Product/%i/Discount/%s" (fun(id, couponCode) -> json (Root.ComposeApplyCouponFlow() (id |> ProductId, couponCode |> CouponCode))
4        RequestErrors.NOT_FOUND "Not Found"
5    ]

IO sandwich automatically leads to ports and adapters. Scott Wlashin talks [4] about this style at NDC Conference but in terms of onion and clean architecture with IO at the edges, where workflows are composed outside the domain.

Let’s compose an use case in C# Composition Root

Let the fun begin. Let me bring my real-project flow;

 1module CloseReportingPeriodFlow = // this closes a report and opens new one.
 2    open Accounting
 3
 4    type IO = {
 5        ReadSalesReport: unit -> Async<SalesInvoicesReport>
 6        GenerateSalesReportId: unit -> Async<SalesInvoicesReportId>
 7        SaveClosedReport: ClosedSalesInvoicesReport -> Async<unit>
 8        StoreNewReport: SalesInvoicesReport -> Async<unit>
 9    }
10
11    let closeReportingPeriod io =
12        async {
13            use transaction = ReadCommitedTransaction.Create()
14            let! openedReport = io.ReadSalesReport()
15            let! newReportId = io.GenerateSalesReportId()
16            let (closedReport, newReport) = Accountant.closeReportingPeriod openedReport DateTime.UtcNow newReportId
17            do! io.SaveClosedReport closedReport;
18            do! io.StoreNewReport newReport
19            transaction.Complete()
20        }

How can we approach this? We can write a class that wraps the closeReportingPeriod function - as you should know F# modules are static classes. But do we have to? Creating dumb wrappers seems like an architectural disaster. F# can call C# Func<> easily so why not to call F# function from C#? Let’s register the Func<> and inject it to the C# controller in which the Func will be executed. Sounds like a good compromise doesn’t it? C# Types in C#, F# in F#. Let’s give it a try!

Step 1 - compose IO

In our C# CompositionRoot let’s create CloseReportingPeriodFlow IO type by writing var io = new CloseReportingPeriodFlow.IO()

Well… FSharpFunc? FSharpAsync? Unit? In C#? No worries! Let’s write

1FSharpFunc<ClosedSalesInvoicesReport, FSharpAsync<Unit>> saveClosedReport = report =>
2    SalesReportDao.saveClosedReport(_fSqlConnectionFactory, report);

Unfortunately ... report => ... is C# Func, not FSharpFunc, so you will end up with this error:

Cannot convert initializer type 'lambda expression' to target type 'Microsoft.FSharp.Core.FSharpFunc<SalesInvoicesReport.ClosedSalesInvoicesReport,Microsoft.FSharp.Control.FSharpAsync<Microsoft.FSharp.Core.Unit>>' Ok… Let’s do it using Func. Let’s give it a try:

1Func<SalesInvoicesReport, FSharpAsync<Unit>> storeNewReport = report =>
2    SalesReportDao.insertNewReport(_fSqlConnectionFactory, report);

Yay! No errors. Let’s pass it to the CloseReportingPeriodFlow.IO ctor… The result:

Argument type 'System.Func<SalesInvoicesReport.ClosedSalesInvoicesReport,Microsoft.FSharp.Control.FSharpAsync<Microsoft.FSharp.Core.Unit>>' is not assignable to parameter type 'Microsoft.FSharp.Core.FSharpFunc<SalesInvoicesReport.ClosedSalesInvoicesReport,Microsoft.FSharp.Control.FSharpAsync<Microsoft.FSharp.Core.Unit>>' So now we can’t pass C# Func as F# Func. To fix this let’s write some interop extension methods which can convert Funcs for us. F# already naturally creates the proper nesting structure for Func so instead of doing this again in C# let’s write them in F#.

 1namespace FSharpCSharpInteroperability
 2
 3open System
 4open System.Runtime.CompilerServices
 5open System.Threading.Tasks
 6
 7[<Extension>]
 8type public FSharpFuncUtil =
 9    [<Extension>]
10    static member ToFSharpFunc<'a,'b> (func:System.Func<'a,'b>) = fun x -> func.Invoke(x)

Basically we wrote a method that takes a C# Func (with one generic parameter ‘a and result of generic type ‘b) and returns FSharpFunc (fun x) that causes the C# Func to invoke with parameter x (of ‘a type). Let’s check if we did well:

1var io = new CloseReportingPeriodFlow.IO(
2    readSalesReport.ToFSharpFunc()
3);

Constructor 'IO' has 4 parameter(s) but is invoked with 1 argument(s) Great! Let’s add the next parameters.

1Func<FSharpAsync<SalesInvoicesReportId>> generateSalesReportId = () =>
2    SalesReportDao.generateSalesReportId(_fSqlConnectionFactory);

No, we don’t have an extension method for converting C# Func that takes nothing and returns something (so action delegate). It should be easier to write than the previous one (without the ‘b right?)

1[<Extension>]
2static member ToFSharpFunc (func:System.Func<'a>) = fun() -> func.Invoke();

Right 🤓 For the last two arguments we have everything we need. Complete IO definition;

 1private Func<Task> ComposeCloseReportingPeriod()
 2{
 3    Func<FSharpAsync<SalesInvoicesReport>> readSalesReport = () =>
 4        SalesReportDao.readOpenedReport(_fSqlConnectionFactory);
 5    Func<ClosedSalesInvoicesReport, FSharpAsync<Unit>> saveClosedReport = report =>
 6        SalesReportDao.saveClosedReport(_fSqlConnectionFactory, report);
 7    Func<SalesInvoicesReport, FSharpAsync<Unit>> storeNewReport = report =>
 8        SalesReportDao.insertNewReport(_fSqlConnectionFactory, report);
 9    Func<FSharpAsync<SalesInvoicesReportId>> generateSalesReportId = () =>
10        SalesReportDao.generateSalesReportId(_fSqlConnectionFactory);
11    var io = new CloseReportingPeriodFlow.IO(
12        readSalesReport.ToFSharpFunc(),
13        generateSalesReportId.ToFSharpFunc(),
14        saveClosedReport.ToFSharpFunc(),
15        storeNewReport.ToFSharpFunc()
16    );
17    return // TODO compose workflow
18}

Step 2 - compose workflow

Let’s try to do partial application now by returning a Func which has the io dependency applied (complete workflow).

1return () => CloseReportingPeriodFlow.closeReportingPeriod(io);

Cannot convert expression type 'Microsoft.FSharp.Control.FSharpAsync<Microsoft.FSharp.Core.Unit>' to return type 'System.Threading.Tasks.Task' As you (should) know in C# programming model, asynchronous methods return objects of type Task<T>. F# has it own type Async<T>. We see it as FSharpAsync (error message). They are not compatible becuase they use different asynchrnous model:

  • C# uses Hot Tasks. In this model asynchronous code returns a Task that already has been started and will eventually produce a value.
  • F# uses Tasks generators. In this model asynchronous code returns an object that will generate and start a task when it is provided with a continuation to be called when the operation completes.

Each has it pros and cons. If you are interested in more details I strongly recommend a series of 4 blog posts from Tomas Petricek who was responsible for selecting asynchronous model for F# [4] together with Don Syme. Back to the problem - we have two options now. We can change the composed type as follows:

1private Func<Task> ComposeCloseReportingPeriod()
2// to:
3private Func<FSharpAsync<Unit>> ComposeCloseReportingPeriod()

But then we will force C# Controllers to use F# types. Let’s decide to not be that aggresive - We can encapsulate whole F#<->C# interopability issues handling in CompositionRoot supported with some helper extensions methods or whatever. Luckily it turned out that we can easily convert FSharpAsync to C# Task;

1public static class FSharpAsyncExtension
2{
3    public static Task<T> StartAsDefaultTask<T>(this FSharpAsync<T> computation) =>
4        FSharpAsync.StartAsTask(
5            computation,
6            new FSharpOption<TaskCreationOptions>(TaskCreationOptions.None),
7            new FSharpOption<CancellationToken>(CancellationToken.None));
8}

Now we can complete the composition:

 1        private Func<Task> ComposeCloseReportingPeriod()
 2        {
 3            Func<FSharpAsync<SalesInvoicesReport>> readSalesReport = () =>
 4                SalesReportDao.readOpenedReport(_fSqlConnectionFactory);
 5            Func<ClosedSalesInvoicesReport, FSharpAsync<Unit>> saveClosedReport = report =>
 6                SalesReportDao.saveClosedReport(_fSqlConnectionFactory, report);
 7            Func<SalesInvoicesReport, FSharpAsync<Unit>> storeNewReport = report =>
 8                SalesReportDao.insertNewReport(_fSqlConnectionFactory, report);
 9            Func<FSharpAsync<SalesInvoicesReportId>> generateSalesReportId = () =>
10                SalesReportDao.generateSalesReportId(_fSqlConnectionFactory);
11            var io = new CloseReportingPeriodFlow.IO(
12                readSalesReport.ToFSharpFunc(),
13                generateSalesReportId.ToFSharpFunc(),
14                saveClosedReport.ToFSharpFunc(),
15                storeNewReport.ToFSharpFunc()
16            );
17            return () => CloseReportingPeriodFlow.closeReportingPeriod(io).StartAsDefaultTask();
18        }

This is extremely easy to register, I have used named parameter because registering Func<Task> type seems to ask for errors. This causes some more code but nothing extraordinary:

1var closeReportingPeriodParam = new ResolvedParameter(
2    (paramInfo, ctx) => paramInfo.ParameterType == typeof(Func<Task>),
3    (paramInfo, ctx) => closeReportingPeriod);
4builder.RegisterInstance(closeReportingPeriod)
5    .Named<Func<Task>>(IoCConsts.CloseReportingPeriodFunc);
6builder.RegisterType<SaleInvoicesReportsController>()
7    .WithParameter(closeReportingPeriodParam);

And the controller looks like this:

 1    [Route(template: "api/[controller]")]
 2    public class SaleInvoicesReportsController : ControllerBase
 3    {
 4        private readonly Func<Task> _closeReportingPeriod;
 5
 6        public SaleInvoicesReportsController(Func<Task> closeReportingPeriod) =>
 7            _closeReportingPeriod = closeReportingPeriod;
 8
 9        [HttpPatch]
10        public Task CloseReport() => _closeReportingPeriod();
11    }

Cool isn’t it? We didn’t cover the situation in which the flow requires some paramters.

Step 3 - compose workflow that actually requires some parameters.

In that case you should register a Func that takes that parameters. Here’s an example:

 1private Func<InvoiceId /*The parameter*/, Task<FileType.File>> ComposeSingleReportFileGenerator()
 2{
 3    Func<FSharpList<CountryIsoCode>> readEuCountries = EuCountries.listEuCountries;
 4    Func<InvoiceId, FSharpAsync<InvoicedServices>> readInvoicedServices = invoiceId =>
 5        ArchivedBillingItemsToSalesInvoiceReportDao.readInvoicedServices(_fSqlConnectionFactory, invoiceId);
 6    Func<Tuple<FSharpList<SalesInvoicesReportLine>, FileType.FileName>, FileType.File>
 7        convertToCsv = report => CsvConverter.convertToCsv(report.Item1, report.Item2);
 8    var io = new PrepareReportFromSingleInvoiceFlow.IO(
 9        AccountingInvoiceDao.GetBy(_cSqlConnectionFactory).ToFSharpFunc(),
10        readEuCountries.ToFSharpFunc(),
11        readInvoicedServices.ToFSharpFunc(),
12        convertToCsv.ToFSharpFunc());
13    Func<InvoiceId, Task<FileType.File>> singleReportFileGenerator = invoiceId =>
14        PrepareReportFromSingleInvoiceFlow.prepareReportFromSingleInvoice(io, invoiceId).StartAsDefaultTask();
15    return singleReportFileGenerator;
16}
17Then register a func ``Func<InvoiceId, Task<FileType.File>>`` in Autofac or whatever you use. I think you get the idea.

Let’s handle events with F# handlers from C# based event dispatcher

First let me show you the EventDispatcher:

 1    public class AutofacEventDispatcher : EventDispatcher
 2    {
 3        private readonly IComponentContext _context;
 4        public AutofacEventDispatcher(IComponentContext context) => _context = context;
 5
 6        public async Task Dispatch<T>(params T[] events) where T : Event
 7        {
 8            if (events == null) return;
 9            foreach (var @event in events)
10            {
11                var handlerType = typeof(IEnumerable<>)
12                    .MakeGenericType(typeof(Events.EventHandler<>)
13                        .MakeGenericType(@event.GetType()));
14                var eventHandlers = (IEnumerable<dynamic>)_context.ResolveOptional(handlerType);
15                var tasks = eventHandlers
16                    .Select(handler => handler.Handle((dynamic)@event))
17                    .Cast<Task>()
18                    .Concat(funcHandlersTasks);
19                await Task.WhenAll(tasks);
20            }
21        }
22    }

It simply asks autofac to resolve handlers based on event type. A class must then implement this interface:

1public interface EventHandler<in T> where T : Event
2{
3    Task Handle(T @event);
4}

We can do this in F# but its not the most idiomatic way. I wrote in introduction that implementing this interface was the main factor that drove me to handle everything in composition root. That’s true but… but we need to modify EventDispatcher as well to make it work with Funcs.

 1    public class AutofacEventDispatcher : EventDispatcher
 2    {
 3        private readonly IComponentContext _context;
 4        public AutofacEventDispatcher(IComponentContext context) => _context = context;
 5
 6        public async Task Dispatch<T>(params T[] events) where T : Event
 7        {
 8            if (events == null) return;
 9            foreach (var @event in events)
10            {
11                var funcHandlersTasks = FindFuncHandlers(@event);
12                var handlerType = typeof(IEnumerable<>)
13                    .MakeGenericType(typeof(Events.EventHandler<>)
14                        .MakeGenericType(@event.GetType()));
15                var eventHandlers = (IEnumerable<dynamic>)_context.ResolveOptional(handlerType);
16                var tasks = eventHandlers
17                    .Select(handler => handler.Handle((dynamic)@event))
18                    .Cast<Task>()
19                    .Concat(funcHandlersTasks);
20                await Task.WhenAll(tasks);
21            }
22        }
23
24        private IEnumerable<Task> FindFuncHandlers<T>(T @event) where T : Event
25        {
26            var funcHandlerType = typeof(Func<,>)
27                .MakeGenericType(@event.GetType(), typeof(Task));
28            var funcHandlerCollectionType = typeof(IEnumerable<>)
29                .MakeGenericType(funcHandlerType);
30            var funcHandlers = (IEnumerable<dynamic>) _context.Resolve(funcHandlerCollectionType);
31            var funcHandlersCasterd = funcHandlers
32                .Select(handler => handler(@event))
33                .Cast<Task>();
34            return funcHandlersCasterd;
35        }
36    }

So I kept the promise! Nothing FSharpish here. Autofac now is looking for Funcs also. Note that if you have void Handle(Event @event) you will have to modify a little bit the code. Now we can write event handler in fsharp like this:

1module AccountingHandler =
2    open SalesInvoicesReport
3
4    let Handle (prepareReportLineFlow: InvoiceId -> Async<Unit>) (event: GlobalEvents.BillingItemsArchived) =
5        let invoiceId = event.InvoiceId |> InvoiceId
6        prepareReportLineFlow invoiceId

and register it using Autofac:

1    private Func<BillingItemsArchived, Task> ComposeBillingItemsArchivedHandler() {
2            ...
3            var io = new PrepareReportLineFlow.IO(...)
4            Func<BillingItemsArchived, Task> handler = @event =>
5                AccountingHandler.Handle(prepareReportLine.ToFSharpFunc(), @event).StartAsDefaultTask();
6            return handler;
7        }

registration:

1builder.RegisterInstance(ComposeBillingItemsArchivedHandler());

That’s it! EventDispatcher will hande activation and calling the Handle function for us! To be hones I am thinking about moving C# EventHandlers to Funcs as well and drop the EventHandler interface.

Summary

I am really happy with what was achieved here. Composition root is still the single place to compose dependencies it only has to do some adjustment on the types to make it “click”. Such way of F# and C# coexistence is really attractive (at least to me) and builds an approachable point in “Let’s introduce F# in our project” discussion. You could always make a separate service to host F# but I found it to be a source of “fear” for other people as they will have to learn Giraffe or Saturn as well. By doing intermediate steps and creating a win & win argument creates a bright future for more F# in your project. To sum up what I’ve discussed let me classify 3 ways of C# and F# integration.;

  1. By Heresy 😈 let’s officially name it “by mixing OO with FP”
  2. By Composition root types adjustments
  3. By hosting as the other microservice

Thanks for reading! Here’s a public gist if you want to take a look on complete FSharpCSharpInteroperability. Stay tuned about integration of Dapper with F# and handling Nullable C# object!

Special Thank You

to Marcin Sikroski who supported me on the task. Without him the integration wouldn’t look so nice :)


Footnotes:
[2] The discussion at the bottom of the blog post is very very interesting! Check it out as well. Mark and readers discuss putting to much responsibility to controller.
[3] I believe that Microsoft stole a useful name. Not so long time ago I was using name Controller in Application layer as well but everyone was surprised - calling Controller from Controller? I convinced my colleagues by asking what is wrong with that? We have something that controls the flow of the HTTP requests and something that controls the flow of our application. We have found consensus in renaming “our” controller to something else. I find that “UseCase” or “Flow” is also fine - I tend to use the second now because it is simply shorter. References:

Websites:
[1] Asynchrouns injection by Mark Seeman [2] Scott Wlashin about Dependency Injection in F#
[3] Stackoverflow question about DI in F# with Tomas Petricek and Scott Wlashin answer

Videos:
[4] Scott Wlashin - Reinventing the Transaction Script (I like to name it: making Transaction Script Sexy)