Providing Value with Trivial Abstraction in F#

Monday, November 23, 2015

This post is part of the F# Advent Calendar in English 2015. Thanks to Sergey Tihon for organizing and letting me be a part of it this year!

Some short time after implementing MembershipProvider for the first time I realized that simply relying on abstractions to make your code extendable wasn't enough. To embrace and encourage change I had to also make those abstractions as small as possible. While the class makes it possible to change implementations, its 27 abstract methods all but guarantee you're throwing some nontrivial amount of NotImplementedExceptions.

Martin Fowler wrote in 2006 about Role Interfaces as a way to keep out of the trap of large abstractions and we also have the Interface Segregation Principle from the SOLID principles as guidance. My journey to F# has essentially been a tale of following these two ideas to their extremes.

The setup

The following C# code is not SOLID or testable, but its signal to noise ratio is quite high.

public class LocationsManager  
{
    public void CreateLocation(string city, string state) 
    {
        if (!States.Contains(state)) throw new ArgumentException()
            
        using (var con = new SqlConnection("MyDb"))
        using (var command = con.CreateCommand()) {
            command.CommandText = 
                @"Insert into Locations ([State], City)
                  Values(@State, @City)";
                      
            command.Parameters.AddWithValue("State", state);
            command.Parameters.AddWithValue("City", city);  
                      
            con.Open();
            command.ExecuteNonQuery();
        }
    }
}

SOLID Principals

To make this code SOLID and testable in C# we might add some strategy patterns (aka Role Interfaces) to separate concerns.

public class LocationsManager  
{
    readonly ILocationRepository _locationRepository;
    readonly IStateValidator _stateValidator;
        
    public LocationsManager(ILocationRepository r, IStateValidator v)
    {
        _locationRepository = r; 
        _stateValidator = v;
    }
    
    public void CreateLocation(string city, string state)
    {
        if (!_stateValidator.IsValid(state)) 
            throw new ArgumentException("Not a valid State");
        _locationRepository.Insert(city, state);
    }
}

Great, now we can inject mocks and stubs to test the interaction of our ILocationRepository and IStateValidator, but we now have 18 lines of code to describe how 2 interfaces interact to create a location. Now we have to define the interfaces and at least one implementation each of ILocationRepository and IStateValidator.

public interface ILocationRepository
{
    void Insert(string city, string state);
}
    
public class LocationRepository : ILocationRepository
{
    public void Insert(string city, string state)
    {
        using (var con = new SqlConnection(Global.ConnectionString))
        using (var command = con.CreateCommand()) {
            command.CommandText = 
                @"Insert into Locations ([State], City)
                  Values(@State, @City)";
                      
            command.Parameters.AddWithValue("State", state);
            command.Parameters.AddWithValue("City", city);  
                      
            con.Open();
            command.ExecuteNonQuery();
        }
    }
}   

public interface IStateValidator
{
    bool IsValid(string state);
}
    
public class StateValidator : IStateValidator
{
    static readonly List<string> ValidStates = new List<string>
    {
        "AL", "AK", "AZ" //etc..
    };
        
    public bool IsValid(string state)
    {
        return ValidStates.Contains(state);
    }
}

We now have SOLID code that is unit testable. Of course we probably have an IOC container to manage our dependencies which is even more code, and our signal to noise ratio compared to the initial code is already incredibly low.

Sobering Fact

In Code Complete, Steve McConnell lists some interesting stats on bugs per lines of code.

Industry Average: about 15 - 50 errors per 1000 lines of delivered code. Microsoft Applications: about 10 - 20 defects per 1000 lines of code during in-house testing

Nevermind the difference between the industry and Microsoft. The important part is that there is a quantifiable number of bugs per lines of code written.

At best, the object oriented abstractions are coarse and verbose when compared with the trivial abstraction we get for free in F#.

What is this magic abstraction I'm referring to? The ability to substitute any function with any other function of the same signature.

Let's say you have some code that calls a function with the signature string -> int but don't like that function's implementation? No problem, just change the name of the function to point to another function with the same signature. This allows us to write code that has a very high signal to noise ratio.

type InsertLocation = 
  SqlCommandProvider<
    "INSERT INTO Locations(City, State)
    Values(@City, @State)",
    "connectionString">

let insertLocation city state =
  use command = new InsertLocation()
  command.Execute(city, state) |> ignore

let stateIsValid state =
  let states = [ "AL";"AK"; .. ]
  states |> List.contains state

let createLocation city state =
  if stateIsValid state
  then insertLocation city state
  else raise (System.ArgumentException("Not a valid State"))

So here in 18 lines of code we have all of the value from the C# example. If we have a better implementation for stateIsValid we can simply change our if stateIsValid state line to target a different function whose signature is string -> bool. Our ability to swap functions in this manner eliminates the need for writing interfaces and implementing them. If everything we write is implicitly treated as an abstraction point then the need to explicitly abstract anything is mitigated.

Note: throwing an exception isn't very functional but is beyond the scope of what I want to cover in this post. For more reading please visit An example of functional flow in F#

Varying the implementation

So what if we need to vary the implementation of stateIsValid? We can trivially accept a function as an argument so that our function relies on the argument rather than the closure (matching named function in scope).

let createLocation stateIsValid city state =
  if stateIsValid state
  then insertLocation city state
  else raise (System.ArgumentException("Not a valid State"))

Now this function is a higher order function because it is a function that accepts a function stateIsValid. Our createLocation function can accept as its first argument any function whose signature is string -> bool.

Were it not for our insertLocation function defining that state is a string the required signature would be the generic 'a -> bool

If you have an OO background this should look very similar to Dependency Injection.

let isUSstate state =
  let states = [ "AL";"AK"; .. ]
  states |> List.contains state
  
let isCAprovince province =
  let provinces = [ "AB";"BC"; .. ]
  states |> List.contains province
  
let createLocation stateIsValid city state =
  if stateIsValid state
  then insertLocation city state
  else raise (System.ArgumentException("Not a valid State"))

We could then use partial application to create helper methods if we wanted

let createUSlocation = createLocation isUSstate

let createCanadianLocation = createLocation isCAprovince

Now both of those functions have the same signature as our original createLocation. They both now accept 2 strings because we've already provided the stateIsValid argument ahead of time. We didn't have to define constructors or maintain private members like we did in the OO example above. We simply order our arguments so that the dependency comes first and the language handles the rest.

Summary

Using a language where every function is a potential abstraction point means that we can immediately focus on providing value rather than architecting patterns and abstractions within our code.

Thank You

This post would not be possible without the awesome F# community who have always been exceedingly helpful and welcoming when I had issues or questions.

References

Each of these links played a major part in me being able to understand functional programming and write a post like this. Thank you Scott Wlaschin and Mark Seemann for taking the time to codify your thoughts.

http://fsharpforfunandprofit.com/series/thinking-functionally.html

http://www.infoq.com/presentations/mock-fsharp-tdd

http://blog.ploeh.dk/2014/03/10/solid-the-next-step-is-functional/