Honest Methods - Introduction
In a previous article, Writing Honest Methods in C#, I talked about honest methods.
An honest method is one that we can understand by looking at its signature alone, i.e., without reading its body.
In that article, I talked about honesty for pure methods and honesty for impure methods.
In a nutshell, a Pure method is a method whose output depends solely on its input arguments and that does not have any side effects. This means that a pure method cannot mutate a parameter, cannot read or mutate global state, cannot read the system timer, cannot read or write file contents, cannot read from or write to the console, etc.
In the same article, I suggested that impure methods should be converted to pure or potentially-pure methods for them to become honest.
A potentially-pure method is a method whose body is pure, but that invokes functions passed to it as parameters that can be pure or impure (the function parameters types can be delegates for example).
In another article, Writing Pure Code in C#, I discussed examples where impure methods were converted to potentially pure methods. For example, a method that initially invoked Console.WriteLine directly, was changed to invoke a delegate of type Action<string> passed to it as a parameter. Refer to that article for more details.
Towards the end of the Writing Honest Methods in C# article, I talked about a problem related to making all impure methods in an application, potentially-pure.
In this article, I will describe this problem in more details and then present a proof-of-concept (POC) of a solution.
This tutorial is from the DotNetCurry(DNC) Magazine with in-depth tutorials and best practices in .NET and JavaScript. This magazine is aimed at Developers, Architects and Technical Managers and covers C#, Patterns, .NET Core, MVC, Azure, DevOps, ALM, TypeScript, Angular, React, and more. Subscribe to this magazine for FREE and receive all previous, current and upcoming editions, right in your Inbox. No Spam Policy.
The example application
In this article, I will work on a command line application that generates reports based on some data in the database and then saves such a report to a file.
The data is related to customer orders; each customer in the database has orders. The report is partitioned by the city in which the customers live. So, the structure of the report looks like the one shown in Figure 1:
Figure 1: Report structure
Figure 2 shows how the methods of the application call each other:
Figure 2: The method call tree
The Main method starts by calling the LoadAllData method to obtain all the data needed to generate the report from the database. It then calls the GenerateReport method passing in the array of City objects it got from LoadAllData.
The City object in this case contains Customer objects representing all the customers living in the city. Each Customer object contains Order objects representing the orders the customer has made.
The GenerateReport method invokes the GenerateReportForCity method for each City object to generate a sub-report for each city and then it aggregates all these sub-reports to generate the full report. We can also expect the GenerateReport method to add other information to the report such as the total number of cities, total number of customers, total number of orders, etc.
The GenerateReportForCity method does something similar, it calls GenerateReportForCustomer for each customer and then aggregates the results into a CityReport object. The GenerateReportForCustomer method also does a similar job. It calls GenerateReportForOrder for each Order object.
Of course, we can expect each of these methods to organize the sub-reports in a different way. For example, GenerateReportForCity might sort the customer reports by customer name, while the GenerateReportForCustomer method might sort the order reports by date or total amount. Also, the additional data these methods add to the aggregated report, is expected to be different.
The GenerateReportForOrder method returns an OrderReport object for the Order object it gets. We can expect this returned report to contain useful details about the order such as the products ordered, total price for each product, the order total price, etc.
In the figure, I colored the Main method in orange, the LoadAllData and the SaveReport methods in red, and the other methods in green. This is the same coloring scheme I used in the Writing Honest Methods in C# article.
In summary, I use red to indicate that a method is impure, green to indicate that a method is pure, and orange to indicate that although the method has a pure body, it calls other methods that are impure. See the mentioned article for more details.
The LoadAllData method is impure because it communicates with the database. The SaveReport method is impure because it writes to the file system. The Main method is impure because it invokes LoadAllData and SaveReport. The other methods contain pure logic.
I have created a sample project to demonstrate the ideas in this article. You can find the source code here: https://github.com/ymassad/ComposingHonestFunctionsExamples
The solution contains ten projects. For now, look at the ReportGenerator project.
The sample application does not read from the database, instead the LoadAllData method returns some constant data. Also, the SaveReport method simply outputs the report to the console. The contents of each *Report object generated by the corresponding Generate* method is simply a string containing a simplistic description of the object passed to the Generate* method.
I did all this to simplify the sample application. In the LoadAllData method, I intentionally increment a static field called DummyState to make the method impure. This is relevant if you decide to use the PurityAnalyzer extension to analyze the application code.
For more information about this extension, see the Writing Pure Code in C# article.
Here is the code for the Main method:
static void Main(string[] args)
{
var cities = LoadAllData();
var report = GenerateReport(cities);
SaveReport(report);
}
Listing 1: The Main method
In the Writing Honest Methods in C# article, I mentioned the concept of the impure/pure/impure sandwich. We can see such a concept applied here in the sample application.
First, the impure LoadAllData method is invoked to get all the data needed to generate the report. Then, the pure GenerateReport method is invoked to generate the report. Then, the impure SaveReport method is invoked to save the report.
Great! So we have separated the pure part from the impure parts in the best way possible!
Well, not really!!
Your manager comes to your office and tells you that some client is having trouble running the application. After investigation, you discover that the client data set is huge and that the call to LoadAllData fails because of this huge data set.
To fix the issue, you decide to make the LoadAllData method not include the Order objects. You defer loading orders for each customer from the database to the GenerateReportForCustomer method. See the updated application code in the ReportGenerator2 project.
Here is how the method call tree looks like now:
Figure 3: Method call tree after making the GenerateReportForCustomer method invoke an impure method to obtain customer orders
The LoadAllData method is now renamed to LoadAllDataWithoutOrders. Also, the GenerateReportForCustomer method calls the impure LoadOrdersForCustomer method to get the Order objects for the customer from the database. To make things simple, the LoadOrdersForCustomer method in the sample application does not speak to the database. Instead, it returns constant data.
Notice how I changed the colors of three Generate* methods to orange. The bodies of these methods are still pure. However, they are impure because they end up calling the LoadOrdersForCustomer method, which is impure.
Now, only the GenerateReportForOrder method is pure. You are no longer happy. Three Generate* methods are no longer honest!
How to fix this?
You decide to make the GenerateReportForCustomer method potentially-pure by making it invoke a getOrdersForCustomer function parameter (which is a new parameter added to this method) instead of invoking the impure LoadOrdersForCustomer method directly. Also, because you want to make GenerateReportForCity and GenerateReport potentially-pure, you make them take a getOrdersForCustomer parameter which they simply pass down.
The updated code is in the ReportGenerator3 project.
Figure 4 shows how the method call tree looks like:
Figure 4: Method call tree after making the three Generate methods potentially-pure
I changed the color of the three Generate* methods back to green. These methods are potentially-pure, not pure. But because potentially-pure methods are honest about their impurities, it makes sense to look at them in a positive way like this. I talk about this in more details in the Writing Honest Methods in C# article.
When calling the GenerateReport method, the Main method passes the impure LoadOrdersForCustomer method as an argument for the getOrdersForCustomer parameter. In some sense, we have brought purity back to the Generate* methods.
Now comes the problem I described at the end of the Writing Honest Methods in C# article. Three Generate* methods have to pass getOrdersForCustomer down the call stack even if they don’t use it directly.
The sample application is a simple one. A real application could have call stacks that have 20 methods in them. Also, the number of parameters that intermediate methods have to pass to the lower methods, can be large.
For example, consider that a client wants to control aspects of order report generation by using command line parameters. You decide to create an OrderReportFormattingSettings class to model such aspects. You create an instance of this class in the Main method, populate it based on the command line arguments, and then pass it through the Generate* methods down to GenerateReportForOrder. The new code is in the ReportGenerator4 project.
Figure 5 shows the method call tree:
Figure 5: Adding the OrderReportFormattingSettings parameter to the Generate* methods
So, whenever a lower-level method requires a new parameter, either some settings related parameter, or some impure functionality, we need to update all the methods from the Main method down to this method so that they pass the new parameter.
This is a maintainability issue.
What is the solution?
Dependency Injection
Well, the solution is Dependency Injection.
Dependency Injection allows us to fix some parameters at composition time and have some parameters be specified at runtime. Consider this class:
public sealed class SomeClass
{
public readonly int valueToAdd;
public SomeClass(int valueToAdd)
{
this.valueToAdd = valueToAdd;
}
public int AddSome(int value) => value + valueToAdd;
}
Listing 2: A class that takes one parameter at composition time and one at runtime
When creating an instance of this class, the value for valueToAdd must be specified. Every time you invoke AddSome, you can pass a different value for the value parameter. However, valueToAdd is fixed because it is set at composition time.
We can convert the four Generate* methods into classes and use Dependency Injection to have the orderReportFormattingSettings and getOrdersForCustomer parameters fixed at composition time, i.e., in the Main method.
The new code is in the ReportGenerator5 project.
Here are how the *Generator classes composed and used in the Main method:
var orderReportGenerator =
new OrderReportGenerator(
orderReportFormattingSettings);
var customerReportGenerator =
new CustomerReportGenerator(
orderReportGenerator,
LoadOrdersForCustomer);
var cityReportGenerator =
new CityReportGenerator(
customerReportGenerator);
var reportGenerator =
new ReportGenerator(
cityReportGenerator);
var report = reportGenerator.Generate(cities);
Listing 3: The four *Generator objects are composed in the Main method
Notice how only the OrderReportGenerator class has a dependency (via a constructor parameter) on orderReportFormattingSettings. Also, only the CustomerReportGenerator class has a dependency on LoadOrdersForCustomer.
Notice also how each *Generator object (except for orderReportGenerator) is given an instance of the lower-level *Generator class at composition time.
If in the future, the OrderReportGenerator class requires more dependencies, we don’t have to change all the classes that use it (directly or indirectly). We only need to change it and then provide the required dependency in the Composition Root (the Main method).
This is highly maintainable.
But what about honesty? Is the code honest?
Let’s start with the *Generator classes themselves. Consider the ReportGenerator class for example. If we look at the signature of the Generate method inside it, it only takes in an array of cities. We cannot know from the Generate method signature alone that it will invoke the CityReportGenerator.Generate method.
Still, one can argue that by looking at the signature of the constructor of ReportGenerator, we know that the ReportGenerator class has a dependency on CityReportGenerator. Since both classes have a single method, we can assume that the Generate method has a dependency on the CityReportGenerator.Generate method.
But that is not good enough.
We could not know that the getOrdersForCustomer parameter of CustomerReportGenerator‘s constructor will eventually be called by the ReportGenerator.Generate method.
One could argue though that since the *Generator classes depend on each other, we can navigate the dependency tree and eventually know about getOrdersForCustomer (and also orderReportFormattingSettings). That is:
1. From the signature of the constructor of ReportGenerator, we know about CityReportGenerator.
2. From the signature of the constructor of CityReportGenerator, we know about CustomerReportGenerator.
3. From the signature of the constructor of CustomerReportGenerator, we know about getOrdersForCustomer.
4. From the signature of the constructor of CustomerReportGenerator, we know also about OrderReportGenerator.
5. From the signature of the constructor of OrderReportGenerator, we know about orderReportFormattingSettings.
This is not as simple as looking into a single method’s signature, but it is doable.
Usually, to get the full benefits of Dependency Injection, we program to interfaces.
For example, instead of having ReportGenerator depend on CityReportGenerator directly, we have it depend on ICityReportGenerator. This would be an interface implemented by CityReportGenerator. Usually this is done to enable the customization of the CityReportGenerator class without modifying it. For example, we can create a decorator for ICityReportGenerator, say CityReportGeneratorLoggingDecorator, that logs to some file the fact that a specific city-level report is being generated.
When that happens, we can no longer navigate the concrete dependencies using the signature of the constructors. When we look at the constructor of the updated ReportGenerator class for example, we see ICityReportGenerator, not CityReportGenerator.
The new code is in the ReportGenerator6 project.
Is the ReportGenerator class honest now?
The ReportGenerator.Generate method has a pure body, and the constructor of the ReportGenerator class tells us that the Generate method invokes ICityReportGenerator.Generate which could be impure. In this sense, we can say the ReportGenerator class is honest.
Looking at this from a different angle, the ReportGenerator class does not tell us what concrete implementation would be behind ICityReportGenerator. This is no longer the job of the ReportGenerator class. The Composition Root (the Main method) is the one responsible for this.
This means we should focus on the Composition Root for our questions about honesty.
Let’s look at it again:
var orderReportGenerator =
new OrderReportGenerator(
orderReportFormattingSettings);
var customerReportGenerator =
new CustomerReportGenerator(
orderReportGenerator,
LoadOrdersForCustomer);
var cityReportGenerator =
new CityReportGenerator(
customerReportGenerator);
var reportGenerator =
new ReportGenerator(
cityReportGenerator);
var report = reportGenerator.Generate(cities);
Listing 4: The four *Generator objects are composed in the Main method
The reportGenerator variable holds an instance of the ReportGenerator class.
Let’s ask the following question: is reportGenerator.Generate honest?
Well, from the signature of this method:
Report Generate(ImmutableArray<City> cities)
…we can’t know it will eventually invoke LoadOrdersForCustomer. However, we can navigate the constructor of ReportGenerator to find that it depends on ICityReportGenerator.
Now, looking back at the Main method, we know that an instance of the CityReportGenerator class will be used as ICityReportGenerator inside the ReportGenerator instance. We can continue the same navigation process we did before and use the code in the Main method to know which concrete implementation is used behind which interface. Doing this to the end, we will know that reportGenerator.Generate eventually calls LoadOrdersForCustomer.
It was hard before. And now it is even harder. I don’t think we can consider reportGenerator.Generate to be honest.
Currently, the dependency tree has a depth of four (we have four *Generator classes). What happens when we have a depth of 20 or 30?
We need something better.
Before discussing the solution, I will talk about a way to do Dependency Injection without using classes.
Partial Invocation
Partial invocation is a functional programming technique that allows us to invoke a function with only some of its parameters giving us back another function, that takes the rest of the parameters.
For example, assume we have the following function, represented by a C# Func:
Func<int, int, int, int> calculate = (x, y, z) => x * y + z;
This function takes three integers as parameters and returns another integer as a result based on the specified equation.
Consider the following code:
Func<int, int, int> calculate1 = (y, z) => calculate(1, y ,z);
This new function takes two integers as parameters and invokes the calculate function passing a constant value of 1 as the first argument, and then the values for y and z as the next two arguments.
In C#, to create calculate1, I had to manually define it to invoke calculate. However, in other languages, there is a built-in support for doing this.
For instance, imagine that we could do something like this:
var calculate1 = calculate(1);
In C#, this code will not compile. But in F# for example, a similar code would compile, and the result would be like how we defined calculate1 the first time.
Editorial Note: Here’s an article written by Damir titled Functional Programming (F#) for C# Developers that talks about some functional approaches you are already using in C#.
Let’s look at the SomeClass class again:
public sealed class SomeClass
{
public readonly int valueToAdd;
public SomeClass(int valueToAdd)
{
this.valueToAdd = valueToAdd;
}
public int AddSome(int value) => value + valueToAdd;
}
Listing 5: A class that takes one parameter at composition time and one at runtime
This class can be replaced by the following function:
Func<int, int, int> AddSome = (valueToAdd, value) => value + valueToAdd;
We can call it directly like this:
var result = AddSome(2, 1);
But we can also partially invoke it (in an imaginary version of C#) like this:
var AddSome2 = AddSome(2);
And then give AddSome2 to any consumer who needs to call SomeClass.AddSome. This would be like constructing SomeClass like this:
var AddSome2 = new SomeClass(2);
The difference is that partial invocation is more flexible. Given a function, we can choose which parameters to fix at composition time and which parameters we need to keep for the callers to specify at runtime.
The same function can be partially invoked in different ways, e.g. different number of fixed parameters at composition time, and then the results can be given to different consumers.
I have been working on a project to enable partial invocation in C#. I will show you sample code and then I will discuss it. See the code in the ReportGenerator7 project.
I have removed the *Generator classes and the corresponding interfaces. I have created a static class called ReportingModuleFunctions that contains public static methods that have the following signatures:
public static Report GenerateReport(
Func<City, CityReport> generateCityReport,
ImmutableArray<City> cities)
public static CityReport GenerateReportForCity(
Func<Customer, CustomerReport> generateCustomerReport,
City city)
public static CustomerReport GenerateReportForCustomer(
Func<Customer, ImmutableArray<Order>> getOrdersForCustomer,
Func<Order, OrderReport> generateOrderReport,
Customer customer)
public static OrderReport GenerateReportForOrder(
OrderReportFormattingSettings orderReportFormattingSettings,
Order order)
Listing 6: The four Generate* methods
These methods are similar to the methods in the ReportGenerator4 project. The difference though is that these methods do not call each other directly.
For example, the GenerateReport method does not call GenerateReportForCity directly, instead it takes a parameter of type Func<City, CityReport> that it uses to generate the sub-report for each city.
These four static methods are the functional equivalent of the four classes used in the ReportGenerator6 project.
For example, the GenerateReport method takes a parameter of type Func<City, CityReport>, and the ReportGenerator class constructor takes a parameter of type ICityReportGenerator which has a single method that takes a City object and returns a CityReport object. Also, the GenerateReport method takes a parameter of type ImmutableArray<City>, and the Generate method in the ReportGenerator class takes a similar parameter.
Like we used Dependency Injection to connect the four classes together in the Main method in the ReportGenerator6 project, in the ReportGenerator7 project we use Partial Invocation to connect the four functions together. Here is how the Main method looks like:
var generateReportForOrder =
ReportingModule.GenerateReportForOrder()
.PartiallyInvoke(orderReportFormattingSettings);
var generateReportForCustomer =
ReportingModule.GenerateReportForCustomer()
.PartiallyInvoke(
generateReportForOrder,
DatabaseModule.LoadOrdersForCustomer());
var generateReportForCity =
ReportingModule.GenerateReportForCity()
.PartiallyInvoke(generateReportForCustomer);
var generateReport =
ReportingModule.GenerateReport()
.PartiallyInvoke(generateReportForCity);
Listing 7: The four Generate* methods are composed together
Listing 7 is very similar to Listing 3. For example, compare how we create the OrderReportGenerator object in Listing 3, to how we obtain the generateReportForOrder function. In Listing 3, we use the constructor to specify orderReportFormattingSettings, and in Listing 7, we use a special method, the PartiallyInvoke method, to specify it. The same goes for the other objects/functions.
ReportGenerator7 is equivalent to ReportGenerator6. We are just using functions instead of objects.
The analysis we did for method honesty for the code in the ReportGenerator6 project, applies here too. We still haven’t found a way to make the functions composed in the Main method, honest.
Before discussing one solution, let’s first talk about the PartiallyInvoke method and the ReportingModule class.
The InterfaceMappingHelper Visual Studio extension
The PartiallyInvoke methods used in the Main method are auto generated methods. If you look in the Solution Explorer window under the Program.cs file in the ReportGenerator7 project, you will see a file called Program.Mapping.cs. This file was auto generated by a Visual Studio extension I developed called InterfaceMappingHelper.
I talked about some features of this extension in the Aspect Oriented Programming in C# via Functions article. The features I used in the ReportGenerator7 project are different ones though. If you look at the properties of the Program.cs file (via the Properties window), you will find that the Custom Tool property has the value of MappingCodeGenerator. This tool is part of the InterfaceMappingHelper extension. For more information about custom tools in Visual Studio, see this reference: https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/custom-tools?view=vs-2017
When we save the Program.cs file, the MappingCodeGenerator Custom Tool runs. It will look for calls to PartiallyInvoke (a dummy extension method inside the FunctionExtensionMethods class) inside Program.cs and generate a real PartiallyInvoke method inside Program.Mapping.cs.
But why do we need to have these methods generated? Why not include them in some library?
The reason is that the number of all the possible variations of the PartiallyInvoke method is huge.
For example, say you have a function with 20 input parameters, and decide to partially invoke the function with 5 arguments. You can choose to pass arguments for parameters 1, 4, 6, 13, and 19. I hope you can now imagine how the number of possible overloads of PartiallyInvoke can become very large.
The ReportingModuleFunctions.cs and the DatabaseModuleFunctions.cs files also have the MappingCodeGenerator tool set as the value for their Custom Tool property.
When the Custom Tool finds a static class whose name ends with “Functions”, it generates a corresponding class in the *.Mapping.cs file whose name is the same as the static class but without “Functions”. For example, a ReportingModule class will be generated for ReportingModuleFunctions.
For each public method in the *Functions class, say MethodX, a public method will be generated in the corresponding class in the *.Mapping.cs file. Such generated method returns a function which simply calls MethodX. This is done for convenience because in C#, there is no convenient way to convert a static method to a function. For example, if you have a static method like this:
public static string FormatDate(DateTime date)
There is no convenient way to obtain a Func<DateTime, string> from this method. For example, there is no way to do something like this:
var formatDateFunc = FormatDate.ToFunc();
We can do it like this:
var formatDateFunc = new Func<DateTime, string>(FormatDate);
But this is not convenient because we have to specify the parameter types and the return type of the FormatDate method.
To see how the Custom Tool helps, consider that the programmer creates a static method of the following signature:
public static OrderReport GenerateReportForOrder(
OrderReportFormattingSettings orderReportFormattingSettings,
Order order)
And now the generated ReportingModule.GenerateReportForOrder method returns a function of type Func<(OrderReportFormattingSettings orderReportFormattingSettings, Order order), OrderReport> representing the static method.
Notice how the two input arguments are put inside a tuple. For more information about this, please see the Aspect Oriented Programming in C# via Functions article.
Note: The InterfaceMappingHelper extension by default understands an interface called IFunction and not the Func delegate. It can however be configured to use the System.Func<T,TResult> delegate. The settings can be found in the Visual Studio menu > Tools > Options > Interface Mapping Helper.
Figure 6 shows how it looks on my machine:
Figure 6: Modifying the settings of the InterfaceMappingHelper extension to make it use the Func<T,TResult> delegate instead of the IFunction<TInput, TOutput> interface
Make sure you have defined the settings correctly on your machine if you want to experiment with the extension.
Let’s talk now about how to make the composed functions in the Main method, honest.
Honest Dependency Injection
Let’s start by looking at the Main method in the ReportGenerator8 project.
var generateReportForOrder =
ReportingModule.GenerateReportForOrder();
var generateReportForCustomer =
ReportingModule.GenerateReportForCustomer()
.HonestlyInject(
generateReportForOrder);
var generateReportForCity =
ReportingModule.GenerateReportForCity()
.HonestlyInject(generateReportForCustomer);
var generateReportHonest =
ReportingModule.GenerateReport()
.HonestlyInject(generateReportForCity);
var generateReport =
generateReportHonest
.PartiallyInvoke(
orderReportFormattingSettings,
DatabaseModule.LoadOrdersForCustomer());
var report = generateReport(cities);
Listing 8: Using the HonestlyInject method in the Main method
Only the Main method is different between the ReportGenerator7 and ReportGenerator8 projects. Note the following differences in the Main method (compare Listing 7 to Listing 8):
- In Listing 7, the PartiallyInvoke method is used to inject functions into other functions, while in Listing 8, a method called HonestlyInject is used.
- In Listing 7, orderReportFormattingSettings is injected into ReportingModule.GenerateReportForOrder immediately. That is, before injecting generateReportForOrder into ReportingModule.GenerateReportForCustomer. In Listing 8, orderReportFormattingSettings is used at a later stage; it is injected into generateReportHonest to get generateReport.
- The same thing is true for DatabaseModule.LoadOrdersForCustomer. In Listing 7, it is injected into ReportingModule.GenerateReportForCustomer immediately. While in Listing 8, it is injected at a later stage into generateReportHonest to get generateReport.
If you hover over the generateReportHonest variable, you will see it has the following type:
Func<
(
ImmutableArray<City>,
Func<Customer, ImmutableArray<Order>>,
OrderReportFormattingSettings
),
Report>
Listing 9: The type of the generateReportHonest variable
generateReportHonest is a function that takes:
1. an array of cities
2. a function that take a customer and returns an array of orders
3. Order report formatting settings
Basically, the HonestlyInject method allows us to inject one function, say function 1, into another function, say function 2, even if function 1 doesn’t exactly match the dependency required by function 2.
Consider for example the ReportingModule.GenerateReportForCustomer function. It has the following signature:
Func<
(
Func<Customer, ImmutableArray<Order>> getOrdersForCustomer,
Func<Order, OrderReport> generateOrderReport,
Customer customer
),
CustomerReport>
Listing 10: Signature of the GenerateReportForCustomer function
This function has a dependency of type Func<Order, OrderReport> which is a function that can generate a sub-report for an order object. We want to inject generateReportForOrder for this dependency, but the generateReportForOrder function has the following signature:
Func<
(
OrderReportFormattingSettings orderReportFormattingSettings,
Order order
),
OrderReport>
Listing 11: The signature of the generateReportForOrder function
It doesn’t exactly match the dependency required by ReportingModule.GenerateReportForCustomer. In the ReportGenerator7 project, we had to first partially invoke GenerateReportForOrder with the orderReportFormattingSettings variable so that the signatures match.
The HonestlyInject method does not have such a requirement. It works even if the injected function has extra parameters. It moves such parameters to the resultant function. Therefore, if you look at the generateReportForCustomer variable, you will see that it has the following type:
Func<
(
Func<Customer, ImmutableArray<Order>>,
Customer,
OrderReportFormattingSettings
),
CustomerReport>
Listing 12: The result of honestly injecting generateReportForOrder into ReportingModule.GenerateReportForCustomer
Notice how the resultant function has an OrderReportFormattingSettings parameter.
The same thing happened when we injected generateReportForCustomer into ReportingModule.GenerateReportForCity. The Func<Customer, ImmutableArray<Order>> parameter of generateReportForCustomer moved to the result of the injection, that is, the function stored in the generateReportForCity variable.
Figure 7 and 8 represents the Composition Roots (the Main methods) for the ReportGenerator7 project and the ReportGenerator8 project:
Figure 7: The Composition Root of ReportGenerator7
Figure 8: The Composition Root of ReportGenerator8
In summary, the HonestlyInject method allows us to delay the injection of impure functions to the last possible moment. This allows us to keep composing pure/potentially-pure functions to the last possible moment.
It is interesting to compare the composed functions in the Main method (in the ReportGenerator8 project) with the Generate* methods in Program.cs in the ReportGenerator4 project. Each composed function in ReportGenerator8 has the same parameters as the corresponding Generate* method in ReportGenerator4.
In some sense, ReportGenerator8 is a more maintainable version of the ReportGenerator4 project. I say “In some sense” because the technique I use in ReportGenerator8 has a lot of issues, see the last section of the article for more details.
The PurityAnalyzer extension can help us verify that the composed function, generateReportHonest, is in fact potentially-pure.
The Main method is impure. It will always be impure because at the end we need to inject impure dependencies.
Can PurityAnalyzer tell us that a part of the Main method is pure?
I have added a new feature in PurityAnalyzer 0.7 that allows developers to verify that some lambda expression is pure.
In PurityAnalyzer’s settings (Tools > Options > Purity Analyzer), make sure you have the “Pure Lambda Full Class Name” and the “Pure Lambda Method Name” set like this:
Figure 9: PureLambda settings
The PureLambda.Create method exists in the CommonCode project. This method takes a Func<T> parameter as input and returns the same Func<T> argument. When PurityAnalyzer sees this method called in code, it checks the purity of the lambda given to the method. This allows us to check the purity of a part of a method.
See the Main method in the ReportGenerator9 project to see how it is used. Try to inject (via PartiallyInvoke) the impure DatabaseModule.LoadOrdersForCustomer function into the composed function inside the lambda passed to PureLambda.Create and see how PurityAnalyzer objects.
The utility of the var keyword
I have used the var keyword to define all the intermediate function variables in the Main method.
This is required because if we change some low-level function to require some new dependency, and then go to the Main method and save it (to force the MappingCodeGenerator custom tool to run), the types of the variables holding the intermediate functions will change. If we use explicit types here, then we will have the same maintenance issue we had originally.
A New Technique
There are many programming languages that treat functions as a first-class citizen and that have the concept of partial invocation built into the language. However, as far as I know, there is not a language/framework that has the concept of honest dependency injection implemented into it.
I have been working on implementing a C# proof-of-concept (POC) for this for months now. I am using the extensibility features provided by Visual Studio and the features provided by the .NET Compiler Platform (Roslyn) to do so. These features of Visual Studio and the C# compiler are great in that they allow us to have something very close to adding new features to the C# language. Having said that, I think it would be great to have support for the concept of honest dependency injection in the C# language itself.
The following are some issues I see in the current solution that I have:
1. The names of the parameters are not maintained when calling PartiallyInvoke or when calling HonestlyInject. Currently, the parameters need to be distinguishable and understandable using their types alone. Even if a parameter is distinguishable using its type at the scope of the function itself, is it also distinguishable when the function is composed with many other functions?
2. How to deal with composite dependencies? For example, what to do when one function has a dependency on an array of functions? How to do honest dependency injection in this case?
3. If many low-level functions have a dependency on some Func<string, Unit> function used to log to the console, then the final generated function will contain many parameters of this type? How to deal with this?
4. What about performance? The generated code adds a lot of code layers. How does this affect performance?
5. The generated code makes navigating the non-generated code a bit more difficult.
6. What happens when the Composition Root becomes large? Can we split it into multiple methods? The return types of methods in C# cannot be ‘var’. How does this affect maintainability?
7. Function parameters currently need to be in the format of Func<(TInput1, TInput2, ..), TOutput> to work with the extension correctly. For convenience, it should be possible to have function parameters with the type Func<TInput1, TInput2,…, TOutput>.
I will be talking about these issues and their potential solutions in the upcoming articles.
Conclusion:
In this article, I discussed the problem of making all methods/functions in an application pure or potentially-pure and provided a proof-of-concept for a solution.
When we make all the methods pure or potentially-pure, all the invocations of impure methods will be converted to function parameters passed into the potentially-pure methods. This means that all methods that end up calling the potentially pure methods will themselves need to have the function parameters required by the underlying methods. This will make the method signatures too long and will make adding a new impure dependency for the low-level methods, a maintainability issue.
The solution is Dependency Injection, or partial invocation if we use functional programming terms.
However, the functions composed in the Composition Root become impure very quickly due to Dependency Injection. In some sense, this makes functions in the Composition Root, dishonest.
To fix this, I talked about a new technique which I call Honest Dependency Injection. Using this technique, we can inject pure/potentially-pure functions into each other without the need to inject impure dependencies first. This technique allows us to delay the injection of impure dependencies to the last possible moment.
This article was technically reviewed by Damir Arh.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Yacoub Massad is a software developer and works mainly on Microsoft technologies. Currently, he works at Zeva International where he uses C#, .NET, and other technologies to create eDiscovery solutions. He is interested in learning and writing about software design principles that aim at creating maintainable software. You can view his blog posts at
criticalsoftwareblog.com. He is also the creator of DIVEX(
https://divex.dev), a dependency injection tool that allows you to compose objects and functions in C# in a way that makes your code more maintainable. Recently he started a
YouTube channel about Roslyn, the .NET compiler.