Transforming imperative code to functional code
In this section, we will transform imperative code to functional code by leveraging method chaining. Suppose we want to create an HTML-ordered list containing the list of the planets in our solar system; the HTML will look as follows:
<ol id="thePlanets"> <li>The Sun/li> <li value="0">Mercury</li> <li value="1">Venus</li> <li value="2">Earth</li> <li value="3">Mars</li> <li value="4">Jupiter</li> <li value="5">Saturn</li> <li value="6">Uranus</li> <li value="7">Neptune</li> </ol>
The imperative code approach
We are going to list the name of planets, including the Sun. We will also mark the order of the planets with the value attribute in each li
element. The preceding HTML code will be displayed in the console. We will create the list in ImperativeCode.csproj
; here you go:
class Program { static void Main(string[] args) { byte[] buffer; using (var stream = Utility.GeneratePlanetsStream()) { buffer = new byte[stream.Length]; stream.Read(buffer, 0, (int)stream.Length); } var options = Encoding.UTF8 .GetString(buffer) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2); var orderedList = Utility.GenerateOrderedList( options, "thePlanets", true); Console.WriteLine(orderedList); } }
In the Main()
method, we create a byte array, buffer, containing the planet stream we generate in other classes. The code snippet is as follows:
byte[] buffer; using (var stream = Utility.GeneratePlanetsStream()) { buffer = new byte[stream.Length]; stream.Read(buffer, 0, (int)stream.Length); }
We can see that there is a class named Utility
, containing the GeneratePlanetStream()
method. This method is used to generate the list of planets in the solar system in a stream format. Let's take a look at the following code in order to find what is inside the method:
public static partial class Utility { public static Stream GeneratePlanetsStream() { var planets = String.Join( Environment.NewLine, new[] { "Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune" }); var buffer = Encoding.UTF8.GetBytes(planets); var stream = new MemoryStream(); stream.Write(buffer, 0, buffer.Length); stream.Position = 0L; return stream; } }
Firstly, it creates a variable named planets
, containing eight planets named separately on a new line. We get the bytes of the ASCII using the GetBytes
method, and then it is converted into a stream. This stream will be returned to the caller function.
In the main
function, we also have variable options, as follows:
var options = Encoding.UTF8 .GetString(buffer) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2);
This will create a dictionary-typed variable, which contains the name of the planet and its order in the solar system. We use LINQ here, but we will discuss it deeper in the next chapter.
Then, we invoke the GenerateOrderedList()
method inside the Utility
class. This method is used to generate an HTML-ordered list containing the order of the planets in the solar system. The code snippet is as follows:
var orderedList = Utility.GenerateOrderedList( options, "thePlanets", true);
If we take a look at the GenerateOrderedList()
method, we will find the following code:
public static partial class Utility { public static string GenerateOrderedList( IDictionary<int, string> options, string id, bool includeSun) { var html = new StringBuilder(); html.AppendFormat("<ol id="{0}">", id); html.AppendLine(); if (includeSun) { html.AppendLine("\t<li>The Sun/li>"); } foreach (var opt in options) { html.AppendFormat("\t<li value="{0}">{1}</li>", opt.Key, opt.Value); html.AppendLine(); } html.AppendLine("</ol>"); return html.ToString(); } }
First, in this method, we create a StringBuilder
function named html
and add an opening ol
tag, which means an ordered list. The code snippet is as follows:
var html = new StringBuilder(); html.AppendFormat("<ol id="{0}">", id); html.AppendLine();
We also have Boolean variable, includeSun
, to define whether we need to include Sun in the list. We get the value of this variable from the argument of the method. After that, we iterate the content of the dictionary we get from argument. This dictionary is generated by LINQ in the Main()
method. We list the content by adding the li
tag. The foreach
keyword is used to achieve this goal. Here is the code snippet:
foreach (var opt in options) { html.AppendFormat("\t<li value="{0}">{1}</li>", opt.Key, opt.Value); html.AppendLine(); }
We can see that AppendFormat
in the StringBuilder
class is similar to String.Format
, and we can pass Key
and Value
from dictionary. Do not forget to insert a new line for each li
tag using the AppendLine
method.
Lastly, we close the ol
tag with the </ol>
tag, which we define in the following snippet:
html.AppendLine("</ol>");
Then, we invoke the ToString()
method to get a bunch of strings from StringBuilder
. Now if we run the code, we will get the output on the console screen, as we discussed earlier.
The functional code approach
We have already developed imperative code in order to construct an HTML-ordered list of planet names, as discussed earlier. Now, from this imperative code, we are going to refactor it to functional code using method chaining. The functional code we build will be at FunctionalCode.csproj
.
The GenerateOrderedList() method
We start with the GenerateOrderedList()
method since we will modify its first three lines. It looks like the following in ImperativeCode.csproj
:
var html = new StringBuilder(); html.AppendFormat("<ol id="{0}">", id); html.AppendLine();
We can refactor the preceding code to this:
var html = new StringBuilder() .AppendFormat("<ol id="{0}">", id) .AppendLine();
The code becomes more natural now since it applies method chaining. However, we are still able to join the AppendFormat()
method with the AppendLine()
method in order to make it simple. To achieve this goal, we need help from method extension. We can create a method extension for StringBuilder
as follows:
public static partial class StringBuilderExtension { public static StringBuilder AppendFormattedLine( this StringBuilder @this, string format, params object[] args) => @this.AppendFormat(format, args).AppendLine(); }
Now, because we have the AppendFormattedLine()
method in the StringBuilder
class, we can refactor our previous code snippet to the following:
var html = new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id);
The code snippet becomes much simpler than earlier. We also have the invocation of AppendFormat()
following AppendLine()
inside the foreach
loop, as follows:
foreach (var opt in options) { html.AppendFormat("\t<li value="{0}">{1}</li>", opt.Key, opt.Value); html.AppendLine(); }
Therefore, we can also refactor the preceding code snippet using the AppendFormattedLine()
function we added inside the StringBuilder
class, as follows:
foreach (var opt in options) { html.AppendFormattedLine( "\t<li value="{0}">{1}</li>", opt.Key, opt.Value); }
Next, we have AppendLine()
inside the conditional keyword if
. We also need to refactor it to apply method chaining using the extension method. We can create the extension method for StringBuilder
named AppendLineWhen()
. The use of this method is to compare the condition we provide, and then it should decide whether or not it needs to be written. The extension method will be as follows:
public static partial class StringBuilderExtension { public static StringBuilder AppendLineWhen( this StringBuilder @this, Func<bool> predicate, string value) => predicate() ? @this.AppendLine(value) : @this; }
Since we now have the AppendLineWhen()
method, we can chain it to the previous code snippet, as follows:
var html = new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id) .AppendLineWhen(() => includeSun, "\t<li>The Sun/li>");
Thus, we are now confident about removing the following code from the GenerateOrderedList()
method:
if (includeSun) { html.AppendLine("\t<li>The Sun/li>"); }
We are also able to make the AppendLineWhen()
method more general so that it not only accepts a string, but also takes a function as an argument. Let's modify the AppendLineWhen()
method to the AppendWhen()
method, as follows:
public static partial class StringBuilderExtension { public static StringBuilder AppendWhen( this StringBuilder @this, Func<bool> predicate, Func<StringBuilder, StringBuilder> fn) => predicate() ? fn(@this) : @this; }
As we can see, the function now takes Func<StringBuilder, StringBuilder> fn
as an argument to replace the string value. So, it now uses the function to decide the conditional with fn(@this)
. We can refactor var html
again with our new method, as follows:
var html = new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id) .AppendWhen( () => includeSun, sb => sb.AppendLine("\t<li>The Sun/li>"));
We have chained two methods so far; they are AppendFormattedLine()
and AppendWhen()
methods. The remaining function we have is foreach
loop that we need to chain to the StringBuilder
object named html
. For this purpose, we create an extension method to a StringBuilder
named AppendSequence()
again, as follows:
public static partial class StringBuilderExtension { public static StringBuilder AppendSequence<T>( this StringBuilder @this, IEnumerable<T> sequence, Func<StringBuilder, T, StringBuilder> fn) => sequence.Aggregate(@this, fn); }
We use the IEnumerable
interface to make this function iterate over the sequence. It also invokes the Aggregate
method in IEnumerable
as an accumulator that counts the increasing sequence.
Now, using AppendSequence()
, we can refactor the foreach
loop and chain the methods to var html
, as follows:
var html = new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id) .AppendWhen( () => includeSun, sb => sb.AppendLine("\t<li>The Sun/li>")) .AppendSequence( options, (sb, opt) => sb.AppendFormattedLine( "\t<li value="{0}">{1}</li>", opt.Key, opt.Value));
The AppendSequence()
method we add takes the options variable as the dictionary input and function of sb
and opt
. This method will iterate the dictionary content and then append the formatted string into StringBuilder sb
. Now, the following foreach
loop can be removed from the code:
foreach (var opt in options) { html.AppendFormattedLine( "\t<li value="{0}">{1}</li>", opt.Key, opt.Value); }
Next is the html.AppendLine("</ol>")
Ā function invocation we want to chain to the var html
Ā variable. This is quite simple because we just need to chain it without making many changes. Now let's take a look at a change in the var html
assignment:
var html = new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id) .AppendWhen( () => includeSun, sb => sb.AppendLine("\t<li>The Sun/li>")) .AppendSequence( options, (sb, opt) => sb.AppendFormattedLine( "\t<li value="{0}">{1}</li>", opt.Key, opt.Value)) .AppendLine("</ol>");
As we can see in the preceding code, we refactor the AppendLine()
method, so it is now chained to the StringBuilder
declaration.
In the GenerateOrderedList()
method, we have the following line of code:
return html.ToString();
We can also refactor the line so that it will be chained to the StringBuilder
declaration in var html
. If we chain it, we will have the following var html
initialization:
var html = new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id) .AppendWhen( () => includeSun, sb => sb.AppendLine("\t<li>The Sun/li>")) .AppendSequence( options, (sb, opt) => sb.AppendFormattedLine( "\t<li value="{0}">{1}</li>", opt.Key, opt.Value)) .AppendLine("</ol>") .ToString();
Unfortunately, if we compile the code now, it will yield the CS0161 error with the following explanation:
'Utility.GenerateOrderedList(IDictionary<int, string>, string, bool)': not all code paths return a value
The error occurs because the method doesn't return any value when it's expected to return a string value. However, since it is functional programming, we can refactor this method in an expression member. The complete GenerateOrderedList()
method will be as follows:
public static partial class Utility { public static string GenerateOrderedList( IDictionary<int, string> options, string id, bool includeSun) => new StringBuilder() .AppendFormattedLine("<ol id="{0}">", id) .AppendWhen( () => includeSun, sb => sb.AppendLine("\t<li>The Sun/li>")) .AppendSequence( options, (sb, opt) => sb.AppendFormattedLine( "\t<li value="{0}">{1}</li>", opt.Key, opt.Value)) .AppendLine("</ol>") .ToString(); }
We have removed the return
keyword from the preceding code. We have also removed the html
variable. We now have a function that has bodies as lambda-like expressions instead of statement blocks. This feature was announced in .NET Framework 4.6.
The Main() method
The Main()
method in FunctionalCode.csproj
is a typical method we usually face when programming in C#. The method flow is as follows: it reads data from the stream into the byte array and then converts those bytes into strings. After that, it performs a transformation to modify that string before passing it to the GenerateOrderedList()
method.
If we look at the starting code lines, we get the following code snippet:
byte[] buffer; using (var stream = Utility.GeneratePlanetsStream()) { buffer = new byte[stream.Length]; stream.Read(buffer, 0, (int)stream.Length); }
We need to refactor the preceding code to be able to be chained. For this purpose, we create a new class named Disposable
, containing the Using()
method. The Using()
method inside the Disposable
class is as follows:
public static class Disposable { public static TResult Using<TDisposable, TResult> ( Func<TDisposable> factory, Func<TDisposable, TResult> fn) where TDisposable : IDisposable { using (var disposable = factory()) { return fn(disposable); } } }
In the preceding Using()
method, we take two arguments: factory
and fn
. The function to which the IDisposable
interface applies is factory
, and fn
is the function that will be executed after declaring the factory
function. Now we can refactor the starting lines in the Main()
method as follows:
var buffer = Disposable .Using( Utility.GeneratePlanetsStream, stream => { var buff = new byte[stream.Length]; stream.Read(buff, 0, (int)stream.Length); return buff; });
Compared to imperative code, we have now refactored the code that reads the stream and stores it in a byte array with the help of theĀ Dispose.Using()
method. We ask the lambda stream function to return the buff content. Now, we have a buffer variable to be passed to the next phase, which is the UTF8.GetString(buffer)
method. What we actually do in the GetString(buffer)
method is transforming and then mapping the buffer to a string. In order to chain this method, we need to create the Map
method extension. The method will look as follows:
public static partial class FunctionalExtensions { public static TResult Map<TSource, TResult>( this TSource @this, Func<TSource, TResult> fn) => fn(@this); }
Since we need to make it a general method, we use a generic type in the arguments of the method. We also use a generic type in the returning value so that it won't return only the string value. Using the generic types, this Map
extension method will be able to transform any static type value into another static type value. We need to use an expression body member for this method, so we use the lambda expression here. Now we can use this Map
method for the UTF8.GetString()
method. The var buffer
initialization will be as follows:
var buffer = Disposable .Using( Utility.GeneratePlanetsStream, stream => { var buff = new byte[stream.Length]; stream.Read(buff, 0, (int)stream.Length); return buff; }) .Map(Encoding.UTF8.GetString) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2);
By applying the Map
method like the preceding code snippet, we don't need the following code anymore:
var options = Encoding .UTF8 .GetString(buffer) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2);
However, the problem occurs since the next code needs variable options as arguments to the GenerateOrderedList()
method, which we can see in following code snippet:
var orderedList = Utility.GenerateOrderedList( options, "thePlanets", true);
To solve this problem, we can use theĀ Map
methods as well to chain the GenerateOrderedList()
method to the buffer variable initialization so that we can remove the orderedList
variable. Now, the code will be look like what is shown in the following:
var buffer = Disposable .Using( Utility.GeneratePlanetsStream, stream => { var buff = new byte[stream.Length]; stream.Read(buff, 0, (int)stream.Length); return buff; }) .Map(Encoding.UTF8.GetString) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2) .Map(options => Utility.GenerateOrderedList( options, "thePlanets", true));
Since the last line of code is the Console.WriteLine()
method, which takes the orderedList
variable as an argument, we can modify the buffer variable to orderedList
. The change will be as follows:
var orderedList = Disposable .Using( Utility.GeneratePlanetsStream, stream => { var buff = new byte[stream.Length]; stream.Read(buff, 0, (int)stream.Length); return buff; }) .Map(Encoding.UTF8.GetString) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2) .Map(options => Utility.GenerateOrderedList( options, "thePlanets", true));
The last line in the GenerateOrderedList()
method is the Console.WriteLine()
method. We will also chain this method to the orderedList
variable. For this purpose, we need to extend a method called Tee
, containing the pipelining technique we discussed earlier. Let's take a look at the following Tee
method extension:
public static partial class FunctionalExtensions { public static T Tee<T>( this T @this, Action<T> action) { action(@this); return @this; } }
From the preceding code, we can see that the output of Tee
will be passed to the input of the Action
function. Then, we can chain the last line using Tee
, as follows:
Disposable .Using( Utility.GeneratePlanetsStream, stream => { var buff = new byte[stream.Length]; stream.Read(buff, 0, (int)stream.Length); return buff; }) .Map(Encoding.UTF8.GetString) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2) .Map(options => Utility.GenerateOrderedList( options, "thePlanets", true)) .Tee(Console.WriteLine);
Tee
can return the HTML generated by the GenerateOrderedList()
method so that we can remove the orderedList
variable from the code.
We can also implement the Tee
method to the lambda expression in the preceding code. We will refactor the following code snippet using Tee
:
stream => { var buff = new byte[stream.Length]; stream.Read(buff, 0, (int)stream.Length); return buff; }
Let's understand what the preceding code snippet is actually doing. First, we initialize the byte array variable buff
to store as many bytes as the length of the stream. It then populates this byte array using the stream.Read
method before returning the byte array. We can also ask the Tee
method to do this job. The code will be as follows:
Disposable .Using( Utility.GeneratePlanetsStream, stream => new byte[stream.Length] .Tee(b => stream.Read( b, 0, (int)stream.Length))) .Map(Encoding.UTF8.GetString) .Split(new[] { Environment.NewLine, }, StringSplitOptions.RemoveEmptyEntries) .Select((s, ix) => Tuple.Create(ix, s)) .ToDictionary(k => k.Item1, v => v.Item2) .Map(options => Utility.GenerateOrderedList( options, "thePlanets", true)) .Tee(Console.WriteLine);
Now, we have a new Main()
method, applying method chaining to approach functional programming.