9
votes

When working in F# and implementing a "service" pattern, such as wrappers around web APIs or databases or USB gadgets, what's the idiomatic way to do this? My inclination is to use e.g. IDatabase and Database, ITwitter and Twitter, ICamera and Camera interfaces and classes just like in C#, allowing for easy test mocks. But I don't want to code "C# in F#" if that's not the standard way to do it.

I considered using DU's, to represent e.g. Camera | TestCamera, but that means putting all the mock code into the production codebase, which sounds horrible. But maybe that's just my OO background speaking.

I also considered making IDatabase a record of functions. I'm still open to that idea, but it seems a bit contrived. Plus it rules out the idea of ever using an IoC controller, or any "MEF-like" plugin capability (at least that I'm aware of).

What's the "idiomatic F#" way of doing this? Just follow the C# service pattern?

3

3 Answers

10
votes

As mentioned in the other answer, using interfaces is fine in F# and that might be a good way to solve the problem. However, if you use more functional transformation-oriented style of programming, you may not need them.

Ideally, most of the actual interesting code should be transformations that do not perform any effects - and so they do not invoke any services. Instead, they just take inputs and produce outputs. Let me demonstrate using a database.

Using a IDatabase service, you might write something like this:

let readAveragePrice (database:IDatabase) = 
  [ for p in database.GetProducts() do
      if not p.IsDiscontinued then
        yield p.Price ]
  |> Seq.average

When written like this, you can provide a mock implementation of IDatabase to test that the averaging logic in readAveragePrice is correct. However, you can also write the code like this:

let calculateAveragePrice (products:seq<Product>) = 
  [ for p in products do
      if not p.IsDiscontinued then
        yield p.Price ]
  |> Seq.average

Now you can test calculateAveragePrice without any mocking - just give it some sample products that you want to process! This is pushing the data access out from the core logic to the outside. So you'd have:

database.GetProducts() |> calculateAveragePrice // Actual call in the system
[ Product(123.4) ] |> calculateAveragePrice     // Call on sample data in the test

Of course, this is a simplistic example, but it shows the idea:

Push the data access code outside of the core logic and keep the core logic as pure functions that implement transformations. Then your core logic will be easy to test - given sample inputs, they should return the correct results!

6
votes

While other people here write that there's nothing wrong with using interfaces in F#, I consider interfaces nothing more than an interop mechanism that enables F# to work with code written in the other .NET languages.

When I write code in C#, I follow the SOLID principles. When used together, the Single Responsibility Principle and the Interface Segregation Principle should ultimately drive you towards defining as small-grained interfaces as possible.

Thus, even in C#, properly designed interfaces should have only a single method:

public interface IProductQuery
{
    IEnumerable<Product> GetProducts(int categoryId);
}

Furthermore, the Dependency Inversion Principle implies that "clients [...] own the abstract interfaces" (APPP, chapter 11), which means that artificial bundles of methods is a poor design anyway.

The only reason to define an interface like the above IProductQuery is that in C#, interfaces are still the best mechanism for polymorphism.

In F#, on the other hand, there's no reason to define a single-member interface. Use a function instead.

You can pass in functions as arguments to other functions:

let calculateAveragePrice getProducts categoryId = 
    categoryId
    |> getProducts
    |> Seq.filter (fun p -> not p.IsDiscontinued)
    |> Seq.map (fun p -> p.Price)
    |> Seq.average

In this example, you'll notice that getProducts is passed as an argument to the calculateAveragePrice function, and its type is not even declared. This fits beautifully with the principle of letting the client define the interface: the type of the argument is inferred from the client's usage. It has the type 'a -> #seq<Product> (because categoryId can be any generic type 'a).

Such a function as calculateAveragePrice is a higher-order function, which is a Functional thing to do.

4
votes

There's nothing wrong with using interfaces in F#.

The textbook FP approach would be to have the client take functions that implement the logic of your component as arguments, but that approach doesn't scale nicely as the number of those functions grows. Grouping them together is a good idea as it improves readability, and using interfaces for that is an idiomatic way of doing it in .NET. It makes sense especially on the boundary of well-defined components, and I think the three interfaces you cite fit that description well.

I've seen DU's used roughly the way you described (providing both production and a fake implementation), but it doesn't sit well with me either.

If anything, records of functions are not idiomatic. They're a poor man's way of bundling together behaviour in FP languages, but if there's one thing object oriented languages do right, it's bundling together behaviour. Interfaces are just a better tool for the job, especially since F# lets you create implementations inline with object expression syntax.