If, like me, you’ve been doing object oriented programming for a while, you’ll be familiar with Dependency Injection. Dependency Injection is a form of Inversion of Control where a class’s dependencies are provided as constructor arguments. Here’s an example:
class Reporter { private readonly IReportRepository reportRepository; private readonly IReportSender reportSender; public Reporter(IReportRepository reportRepository, IReportSender reportSender) { this.reportRepository = reportRepository; this.reportSender = reportSender; } public void SendReportById(int id) { var report = reportRepository.GetReportById(id); reportSender.SendReport(report); } }
When we instantiate an instance of Reporter, we provide implementations of both IReportRepository and IReportSender. We then call SendReportById which executes methods on both of these dependencies. It’s a two stage process:
1. Create a Reporter with the dependencies it needs to do its job.
2. Execute the Reporter.
In a functional language like F# we can do the same thing without the overhead of having to declare classes and interfaces. We can also do the same thing in C#, but it requires some jiggery-pokery, so I’m going to show you it in F#.
let reporter reportRepository reportSender id = let report = reportRepository id reportSender report
Note: three lines of code verses nine for the C# version.
Here we’re defining a reporter function that takes three arguments, a reportRepository, a reportSender and an id. Internally it does exactly the same as our Reporter class above. In our object oriented version, we explicitly defined the dependencies as constructor arguments so that we can supply dependencies independently of executing the SendReportById method. But because functional languages support currying, we don’t need those constructor arguments, we can partially apply the function instead. We can call reporter with just the first two arguments to create a curried version of reporter that already has the reportRepository and reportSender dependencies satisfied. It’s then just waits for the last argument, the ‘id’ value, in order to execute.
If we execute this definition in F# interactive we see that it has this type:
val reporter : ('a -> 'b) -> ('b -> 'c) -> 'a -> 'c
We can see that it takes two functions and a value and returns a value. We can also see that F# functions are curried by default. We can pass just the first two arguments (‘a –> b’) –> (‘b –> ‘c) and the return value will be ‘a –> ‘c.
Let’s define two simple implementations of reportRepository and reportSender:
type report = { id:int; title:string; body:string } let myReportRepository id = { id = id; title = "Great Report"; body = "The report body" } let myReportSender report = printfn "report = %A" report
New we can build a report sender that uses these two functions as dependencies:
let myReporter = reporter myReportRepository myReportSender
Now myReporter is equivalent to an instance of our Reporter class above. When we execute it, it’s the same as calling myReporter.SendReportById(someId):
> myReporter 10;; report = {id = 10; title = "Great Report"; body = "The report body";} val it : unit = ()
One could argue that the whole paraphernalia of object oriented programming is simply to make up for the lack of currying.