Chapter 10. Taking an Action in C# Functional Programming

This is the most important chapter of this book since we will create a new application using a functional approach. We have already discussed functional programming in depth in the previous chapters, including functional programming concepts, Language Integrated Query (LINQ), recursion, optimizing, and patterns. What we are going to do now is develop an application with an imperative approach and then refactor it into a functional approach.

In this chapter, we will create a Windows forms application and explore how to create a form and then add the code to it. After finishing this chapter, we will be able to refactor the Windows form application from an imperative approach into a functional approach.

In this chapter, we will cover the following topics:

  • Creating a Windows forms application
  • Exploring how to create a form and then add the code to it
  • Creating engine code in an imperative approach
  • Transform the engine code from an imperative to a functional approach

Developing functional programming in Windows forms

Now, we are going to develop a calculator application in a Windows forms application. For this purpose, we have to create a new Windows forms project and a new form with several buttons to contain the numbers 0 to 9 and additional functionality, such as the following screenshot:

Developing functional programming in Windows forms

As you can see, we have 10 buttons that represent the numbers 0 to 9 and standard mathematical operators such as add (+), subtract (-), multiply (*), and divide (/). We also have some additional function buttons; they are square root (sqrt), percent (%) and inverse (1/x). The rest includes these buttons: switch sign (+/-), decimal (.), Clear Entry (CE), Clear All (C), and Backspace (del). We also have a textbox to display the number we entered and set at the top of the form. Last but not least, there is always an equal button in all calculator applications. We give names to all these controls, as shown in the following code snippet:

namespace CalculatorImperative 
{ 
  partial class Form1 
  { 
    private System.Windows.Forms.Button btn0; 
    private System.Windows.Forms.Button btn1; 
    private System.Windows.Forms.Button btn2; 
    private System.Windows.Forms.Button btn3; 
    private System.Windows.Forms.Button btn4; 
    private System.Windows.Forms.Button btn5; 
    private System.Windows.Forms.Button btn6; 
    private System.Windows.Forms.Button btn7; 
    private System.Windows.Forms.Button btn8; 
    private System.Windows.Forms.Button btn9; 
    private System.Windows.Forms.Button btnSwitchSign; 
    private System.Windows.Forms.Button btnDecimal; 
    private System.Windows.Forms.Button btnAdd; 
    private System.Windows.Forms.Button btnDivide; 
    private System.Windows.Forms.Button btnMultiply; 
    private System.Windows.Forms.Button btnSubstract; 
    private System.Windows.Forms.Button btnEquals; 
    private System.Windows.Forms.Button btnSqrt; 
    private System.Windows.Forms.Button btnPercent; 
    private System.Windows.Forms.Button btnInverse; 
    private System.Windows.Forms.Button btnDelete; 
    private System.Windows.Forms.Button btnClearAll; 
    private System.Windows.Forms.Button btnClearEntry; 
    private System.Windows.Forms.TextBox txtScreen; 
  } 
} 

After we have all these controls, the following code snippet contains only the control's name and click events, if any, that we have to set in order to ease the creation of this app since the control's name is unchanged:

namespace CalculatorImperative 
{ 
  partial class Form1 
  { 
    private void InitializeComponent() 
    { 
      this.btn0.Name = "btn0"; 
      this.btn0.Click += 
        new System.EventHandler(this.btnNumber_Click); 
      this.btn1.Name = "btn1"; 
 
      // The rest of code can be found  
      // in the downloaded source code 
    } 
  } 
} 

Additional settings, such as the control's axis location, font, or alignment, don't matter since the settings won't affect the entire code.

Creating the code behind a form

All controls in the form are set and we are now ready to add some code to it. As you can see in all the event clicks in the previous code snippet, there are five functions that will be called for a specific button when pressed: btnNumber_Click(), btnFunction_Click(), btnEquals_Click(), btnClear_Click(), and btnOperator_Click().

The btnNumber_Click() function is for the 0 to 9 button. The btnFunction_Click() function is for the btnSwitchSign, btnDecimal, btnSqrt, btnPercent, btnInverse, and btnDelete button. The btnEquals_Click() function is for the btnEquals buttons. The btnClear_Click() function is for the btnClearAll and btnClearEntry buttons. And btnOperator_Click() is for the btnAdd, btnSubstract, btnDivide, and btnMultiply buttons. Also, there will be some helper functions that we will discuss.

Now let's look at the following code snippet, which contains the implementation of the btnNumber_Click() function:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private void btnNumber_Click(object sender, EventArgs e) 
    { 
      Button btnNum = sender as Button; 
      int numValue; 
      switch (btnNum.Name) 
      { 
        case "btn1": 
          numValue = 1; 
          break; 
        case "btn2": 
          numValue = 2; 
          break; 
        case "btn3": 
          numValue = 3; 
          break; 
        case "btn4": 
          numValue = 4; 
          break; 
        case "btn5": 
          numValue = 5; 
          break; 
        case "btn6": 
          numValue = 6; 
          break; 
        case "btn7": 
          numValue = 7; 
          break; 
        case "btn8": 
          numValue = 8; 
          break; 
        case "btn9": 
          numValue = 9; 
          break; 
        default: 
          numValue = 0; 
          break; 
      } 
      CalcEngine.AppendNum(numValue); 
      UpdateScreen(); 
    } 
  } 
} 

As you can see from the preceding code snippet, the btnNumber_Click() function will detect the pressed number button and then display it in the textbox. For now, let's skip the CalcEngine.AppendNum() and UpdateScreen() functions since we are going to discuss them later.

Let's move on to the btnFunction_Click() function, which will take an action if one of the functional buttons is pressed. The implementation of the function is as follows:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private void btnFunction_Click(object sender, EventArgs e) 
    { 
      Button btnFunction = sender as Button; 
      string strValue; 
      switch (btnFunction.Name) 
      { 
        case "btnSqrt": 
          strValue = "sqrt"; 
          break; 
        case "btnPercent": 
          strValue = "percent"; 
          break; 
        case "btnInverse": 
          strValue = "inverse"; 
          break; 
        case "btnDelete": 
          strValue = "delete"; 
          break; 
        case "btnSwitchSign": 
          strValue = "switchSign"; 
          break; 
        case "btnDecimal": 
          strValue = "decimal"; 
          break; 
        default: 
          strValue = ""; 
          break; 
      } 
      CalcEngine.FunctionButton(strValue); 
      UpdateScreen(); 
    } 
  } 
} 

As you can see from the preceding code snippet, btnFunction_Click() will take action when the btnSqrt, btnPercent, btnInverse, btnDelete, btnSwitchSign, or btnDecimal buttons are pressed.

For the function that is responsible when one of the operator buttons is pressed, here is the code snippet of the btnOperator_Click() function implementation:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private void btnOperator_Click(object sender, EventArgs e) 
    { 
      Button btnOperator = sender as Button; 
      string strOperator = ""; 
      switch (btnOperator.Name) 
      { 
        case "btnAdd": 
          strOperator = "add"; 
          break; 
        case "btnSubtract": 
          strOperator = "subtract"; 
          break; 
        case "btnMultiply": 
          strOperator = "multiply"; 
          break; 
        case "btnDivide": 
          strOperator = "divide"; 
          break; 
      } 
      CalcEngine.PrepareOperation( 
        strOperator); 
      UpdateScreen(); 
    } 
  } 
} 

The preceding btnOperator() function will be used to run the operation of each operator: add, subtract, multiply, and divide. It then calls the PrepareOperation() method in the CalcEngine class, which we will discuss later.

To clear an entry or all entries, we have two buttons: btnClearEntry and btnClearAll. These two buttons will call the btnClear_Click() method every time the press event is generated. The implementation of this function is as follows:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private void btnClear_Click(object sender, EventArgs e) 
    { 
      if (sender is System.Windows.Forms.Button) 
      { 
        Button btnClear = sender as Button; 
        switch (btnClear.Name) 
        { 
          case "btnClearAll": 
            CalcEngine.ClearAll(); 
            UpdateScreen(); 
            break; 
          case "btnClearEntry": 
            CalcEngine.Clear(); 
            UpdateScreen(); 
            break; 
        } 
      } 
    } 
  } 
} 

There are two methods in the CalcEngine class, as well, which are called when these two clearing buttons are pressed: CalcEngine.Clear() for the btnClearEntry button and CalcEngine.ClearAll() for the btnClearAll button.

The last button we have is the btnEquals button, which will call the btnClear_Click() method every time it is pressed; the implementation as follows:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private void btnEquals_Click(object sender, EventArgs e) 
    { 
      //Attempt to solve the math 
      if (!CalcEngine.Solve()) 
      { 
        btnClearAll.PerformClick(); 
      } 
      UpdateScreen(); 
    } 
  } 
} 

From the preceding code snippet, when the btnEquals button is pressed, it tries to calculate the operation the user has given before calling the CalcEngine.Solve() method and then updating the textbox. If the calculation fails, it will clear the entries.

Now, let's create the UpdateScreen() method, which is used to display the current digit to the txtScreen textbox. The implementation is as follows:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private void UpdateScreen() 
    { 
      txtScreen.Text = FormatDisplay( 
        Convert.ToString( 
        CalcEngine.GetDisplay())); 
    } 
  } 
} 

Inside the UpdateScreen() method, the FormatDisplay() method is called to form the display on txtScreen. The implementation of the FormatDisplay() method is as follows:

namespace CalculatorImperative 
{ 
  public partial class Form1 : Form 
  { 
    private string FormatDisplay( 
      string str) 
    { 
      String dec = ""; 
      int totalCommas = 0; 
      int pos = 0; 
      bool addNegative = false; 
 
      if (str.StartsWith("-")) 
      { 
        str = str.Remove(0, 1); 
        addNegative = true; 
      } 
 
      if (str.IndexOf(".") > -1) 
      { 
        dec = str.Substring( 
          str.IndexOf("."), 
        str.Length - str.IndexOf(".")); 
        str = str.Remove( 
          str.IndexOf("."), 
          str.Length - str.IndexOf(".")); 
      } 
 
      if (Convert.ToDouble(str) < 
        Math.Pow(10, 19)) 
      { 
        if (str.Length > 3) 
        { 
          totalCommas = 
            (str.Length - (str.Length % 3)) / 3; 
 
          if (str.Length % 3 == 0) 
          { 
            totalCommas--; 
          } 
 
          pos = str.Length - 3; 
          while (totalCommas > 0) 
          { 
            str = str.Insert(pos, ","); 
            pos -= 3; 
            totalCommas--; 
          } 
        } 
      } 
 
      str += "" + dec; 
      if (str.IndexOf(".") == -1) 
      { 
        str = str + "."; 
      } 
 
      if (str.IndexOf(".") == 0) 
      { 
        str.Insert(0, "0"); 
      } 
      else if (str.IndexOf(".") == 
        str.Length - 2 &&  
        str.LastIndexOf("0") ==  
        str.Length - 1) 
      { 
        str = str.Remove(str.Length - 1); 
      } 
 
      if (addNegative) 
      { 
        str = str.Insert(0, "-"); 
      } 
 
      return str; 
    } 
  } 
} 

Based on the preceding FormatDisplay() function implementation, the first thing that happens is that the function checks whether it is a negative number. If it does, the negative will be removed first and then the addNegative flag will be true, as shown in the following code snippet:

if (str.StartsWith("-")) 
{ 
  str = str.Remove(0, 1); 
  addNegative = true; 
} 

It then looks for the dot (.) character to indicate that it's a decimal number. If the dot is found, it will store the fraction in the dec variable and the rest in the str variable, as shown in the following code snippet:

if (str.IndexOf(".") > -1) 
{ 
  dec = str.Substring( 
    str.IndexOf("."), 
    str.Length - str.IndexOf(".")); 
  str = str.Remove( 
    str.IndexOf("."), 
    str.Length - str.IndexOf(".")); 
} 

Now, the function will make sure that the number is less than 1019. If it is, the following code snippet will format the number:

if (Convert.ToDouble(str) <  
  Math.Pow(10, 19)) 
{ 
  if (str.Length > 3) 
  { 
    totalCommas = 
      (str.Length - (str.Length % 3)) / 3; 
 
    if (str.Length % 3 == 0) 
    { 
      totalCommas--; 
    } 
 
    pos = str.Length - 3; 
    while (totalCommas > 0) 
    { 
      str = str.Insert(pos, ","); 
      pos -= 3; 
      totalCommas--; 
    } 
  } 
} 

The result from the preceding format will be joined with the dec variable. If there's no fraction in the dec variable, the dot character will be added to the last position, as shown in the following code snippet:

str += "" + dec; 
if (str.IndexOf(".") == -1) 
{ 
  str = str + "."; 
} 

If only the fraction number is available, the 0 character will be added at the first position, as shown in the following code snippet:

if (str.IndexOf(".") == 0) 
{ 
  str.Insert(0, "0"); 
} 
else if (str.IndexOf(".") == 
  str.Length - 2 && 
  str.LastIndexOf("0") == 
  str.Length - 1) 
{ 
  str = str.Remove(str.Length - 1); 
} 

Lastly, we check whether the addNegative flag is true. If it is, the negative mark (-) will be added at the first position, as follows:

if (addNegative) 
{ 
  str = str.Insert(0, "-"); 
} 

Creating the engine code in an imperative approach

We have successfully created the code behind the form. Now let's create the engine code in a wrapped class named CalcEngine. We will design it in a CalcEngine.cs file in the CalculatorImperative.csproj project.

Preparing class properties

In this calculator engine class, we need some properties to hold a particular value to be involved in the calculation process. The following code snippet is the class properties' declaration we are going to use in the calculation process:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    // This is the behind the scenes number  
    // that represents what will be on the display  
    // and what number to store as last input 
    private static string m_input; 
 
    // Sign of the number (positive or negative) 
    private static string m_sign; 
 
    // Current operator selected (+, -, * or /) 
    public static String m_operator; 
 
    // Last result displayed 
    private static String m_lastNum; 
 
    // Last input made 
    private static String m_lastInput; 
 
    // If the calculator should start a new input 
    // after a number is hit 
    public static bool m_wait; 
 
    // If the user is entering in decimal values 
    public static bool m_decimal; 
 
    // If the last key that was hit was the equals button 
    private static bool m_lastHitEquals;  
  } 
} 

As you can see, we have eight properties that will be involved in the calculation process. The m_input property will hold all the values we have inputted and the formatting number m_sign will store whether the number is + or -. The m_operator property will store the operator, which is + for addition, - for subtraction, * for multiplication, and / for division. The m_lastNum property will hold the result of the calculation. The m_lastInput property will save the last number the user has inputted. The m_wait property is a flag that indicates that the number has been inputted and it's time to wait for the operator and the next number. The m_decimal property flag indicates whether or not it's a decimal number. And the m_lastHitEquals property flag indicates whether btnEquals has been pressed.

Constructing the constructor

In every class, it's best to have a constructor to prepare the properties of the class. It's the same with this class as well. The following is the code snippet of the class constructor implementation:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    static CalcEngine() 
    { 
      // "." is used to represent no input 
      // which registers as 0 
      m_input = "."; 
 
      m_sign = "+"; 
      m_operator = null; 
      m_lastNum = null; 
      m_lastInput = null; 
      m_wait = false; 
      m_decimal = false; 
      m_lastHitEquals = false; 
    } 
  } 
} 

As you can see from the preceding code snippet, if we want to reset all the class properties we have to call the constructor, which is CalcEngine(). For m_input, we use the dot (.) character to indicate that there is no user inputted. We also use the static modifier since the class will be called directly by stating the class name instead of the instance of the class.

Clearing the properties

Earlier, we discussed that we have two clearing methods: ClearAll() and Clear(), as shown in the following code snippet:

switch (btnClear.Name) 
{ 
  case "btnClearAll": 
    CalcEngine.ClearAll(); 
    UpdateScreen(); 
    break; 
  case "btnClearEntry": 
    CalcEngine.Clear(); 
    UpdateScreen(); 
    break; 
} 

The preceding code snippet is extracted from the btnClear_Click() method. Here is the implementation of the ClearAll() method:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    // Resets all variables 
    public static void ClearAll() 
    { 
      //Reset the calculator 
      m_input = "."; 
      m_lastNum = null; 
      m_lastInput = null; 
      m_operator = null; 
      m_sign = "+"; 
      m_wait = false; 
      m_decimal = false; 
      m_lastHitEquals = false; 
    } 
  } 
} 

The ClearAll() method will reset all properties the CalcEngine class has. This is similar to the class constructor implementation. So, we can modify the class constructor implementation as follows:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    static CalcEngine() 
    { 
      ClearAll(); 
    } 
  } 
} 

We also have the Clear() method to clear the last entry only. For this purpose, we just need to reset m_sign, m_input, and m_decimal. The implementation of the Clear() method is as follows:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    // For Clear Entry,  
    // just reset appropriate variable 
    public static void Clear() 
    { 
      //Just clear the current input 
      m_sign = "+"; 
      m_input = "."; 
      m_decimal = false; 
    } 
  } 
} 

Appending the number to the display box

As we know, we have a textbox to display the number we have inputted or to display the result of calculation. In the btnNumber_Click() method implementation, we call the CalcEngine.AppendNum() method, and here is its implementation:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    // Appends number to the input 
    public static void AppendNum( 
      double numValue) 
    { 
      if (numValue == Math.Round(numValue) && 
        numValue >= 0) 
      { 
         // The rest of code can be found  
         // in the downloaded source code 
      } 
      // If they're trying to append a decimal or negative,  
      // that's impossible so just replace the entire input 
      // with that value 
      else 
      { 
         // The rest of code can be found  
         // in the downloaded source code 
      } 
    } 
  } 
} 

From the preceding code, we can see that we have to distinguish the number with a negative sign or a decimal number with a dot mark. For this purpose, we use the following code snippet:

if (numValue == Math.Round(numValue) && 
    numValue >= 0) 

If it's a pure number without a negative number or decimal mark, we check whether m_input is empty or whether the m_wait flag is true. If it is, we can continue the process. If the decimal flag is on, we don't need to insert the dot mark anymore; otherwise, we have to add the dot mark. The following code snippet will explain more about our explanation:

if (!IsEmpty()) 
{ 
  // if decimal is turned on 
  if (m_decimal) 
  { 
    m_input += "" + numValue; 
  } 
  else 
  { 
    m_input = m_input.Insert( 
      m_input.IndexOf("."), "" + numValue); 
  } 
} 

As you can see, we call the IsEmpty() function to check whether m_input is empty or the m_wait flag is true. The implementation of the function is as follows:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    // Indicate that user doesn't input value yet 
    private static bool IsEmpty() 
    { 
      if (m_input.Equals(".") || m_wait) 
        return true; 
      else 
        return false; 
    } 
  } 
} 

If IsEmpty() returns true, it will continue the process, as shown in the following code snippet:

if (m_lastHitEquals)  
{ 
  ClearAll(); 
  m_lastHitEquals = false; 
} 
 
if (m_decimal) 
{ 
  m_input = "." + numValue; 
} 
else 
{ 
  m_input = numValue + "."; 
} 
m_wait = false; 

From the preceding code, first, we check whether the m_lastHitEquals flag is on. If it is, we reset all class properties and then set m_lastHitEquals to off. Then, we check whether the m_decimal flag is on. If it is, insert the dot mark in front of the number. If not, insert the dot mark behind the number. After that, turn off the m_wait flag.

We also have to make sure there are no unnecessary zeroes that have been inserted using the following code snippet:

if (m_input.IndexOf("0", 0, 1) == 0 && 
  m_input.IndexOf(".") > 1) 
{ 
  //Get rid of any extra zeroes  
  //that may have been prepended 
  m_input = m_input.Remove(0, 1); 
} 

The preceding code will handle the user input if it doesn't contain a negative mark (-) or dot mark. If it does, we have to check whether it has these marks or not using the following code snippet:

if (m_input.Contains(".") && 
  !(m_input.EndsWith("0") && 
  m_input.IndexOf(".") == 
  m_input.Length - 2)) 
{ 
  m_decimal = true; 
} 
 
if (m_input.Contains("-")) 
{ 
  m_sign = "-"; 
} 
else 
{ 
  m_sign = "+"; 
} 

However, before we perform the preceding process, we have to reset all the class properties and reformat the number as follows:

// Start over if the last key hit  
// was the equals button  
// and no operators were chosen 
if (m_lastHitEquals)  
{ 
  ClearAll(); 
  m_lastHitEquals = false; 
} 
m_input = "" + numValue; 
 
// Reformat 
m_input = FormatInput(m_input); 
if (!m_input.Contains(".")) 
{ 
  m_input += "."; 
} 

Again, we remove unnecessary zeroes and turn off the m_wait flag as follows:

// Get rid of any extra zeroes 
// that may have been prepended or appended 
if (m_input.IndexOf("0", 0, 1) == 0 && 
  m_input.IndexOf(".") > 1) 
{ 
  m_input = m_input.Remove(0, 1); 
} 
 
if (m_input.EndsWith("0") &&  
  m_input.IndexOf(".") == m_input.Length - 2) 
{ 
  m_input.Remove(m_input.Length - 1); 
} 
 
m_wait = false; 

Preparing the mathematical operation

When we press one of the operator buttons, the btnOperator_Click() function will be fired; inside the function, there is a CalcEngine.PrepareOperation() function to prepare the calculation. The implementation of the CalcEngine.PrepareOperation() function is as follows:

namespace CalculatorImperative 
{ 
  internal class CalcEngine 
  { 
    // Handles operation functions 
    public static void PrepareOperation( 
      string strOperator) 
    { 
      switch (strOperator) 
      { 
         // The rest of code can be found  
         // in the downloaded source code 
      } 
    } 
  } 
} 

The explanation of the preceding code is straightforward. We just need to know which button is pressed, +, -, *, or /. Then, we check whether it is the first number that the user inputs by checking whether m_lastNum is null or not or whether m_wait is on. If it is, we solve the calculation after we make sure that m_lastNum is not null, m_lastHitEquals is off, m_wait is off, and the current m_operator is different from the operator, which was just pressed by a user. After this, we replace m_operator with the current operator that the user inputs and fill m_lastNum with m_input that has been formatted. Also, other settings will have to be applied. The following code snippet will explain this better:

// If this is the first number  
// that user inputs 
if (m_lastNum == null || 
  m_wait) 
{ 
  if (m_lastNum != null && 
    !m_operator.Equals("+") && 
    !m_lastHitEquals && 
    !m_wait) 
  Solve(); 
  m_operator = "+"; 
  m_lastNum = "" + FormatInput(m_input); 
  m_sign = "+"; 
  m_decimal = false; 
  m_wait = true; 
} 

Otherwise, if it's not the first number the user inputs, we can perform the following process:

else 
{ 
    if (!m_wait) 
        Solve(); 
    m_operator = "+"; 
    m_sign = "+"; 
    m_wait = true; 
} 

Formatting the input

Before we go to the Solve() function implementation we discussed in the previous PrepareOperation() function, let's discuss the FormatInput() function first. The following is the implementation of the FormatInput() method:

namespace CalculatorImperative 
{ 
    internal class CalcEngine 
    { 
        // Formats the input into a valid double format 
        private static string FormatInput( 
            string str) 
        { 
            // Format the input to something convertable  
            // by Convert.toDouble 
 
            // Prepend a Zero  
            // if the string begins with a "." 
            if (str.IndexOf(".") == 0)  
            { 
                str = "0" + str; 
            } 
 
            // Appened a Zero  
            // if the string ends with a "." 
            if (str.IndexOf(".") ==  
                str.Length - 1)  
            { 
                str = str + "0"; 
            } 
 
            // If negative is turned on  
            // and there's no "-"  
            // in the current string 
            // then "-" is prepended 
            if (m_sign.Equals("-") &&  
                str != "0.0" &&  
                str.IndexOf("-") == -1)  
            { 
                str = "-" + str; 
            } 
 
            return str; 
        } 
    } 
} 

The FormatInput() method is used to form the number that will be shown in the txtScreen textbox.

Solving the calculation

When we press the btnEquals button or the operator button that has the previous input, the Solve() method will be invoked to calculate the operation. The following is the implementation of the method:

namespace CalculatorImperative 
{ 
    internal class CalcEngine 
    { 
        // Solve the currently stored expression 
        public static bool Solve() 
        { 
            bool canSolve = true; 
             
            // The rest of code can be found  
            // in the downloaded source code 
 
            return canSolve; 
        } 
    } 
} 

Calculating the additional operation

As we have discussed, we have other functional buttons: the btnSqrt, btnPercent, btnInverse, btnDelete, btnSwitchSign, and btnDecimal buttons. Here is the method that will be invoked if one of these buttons is pressed:

namespace CalculatorImperative 
{ 
    internal class CalcEngine 
    { 
        // Handles decimal square roots,  
        // decimal buttons, percents, inverse, delete,  
        // and sign switching 
        public static bool FunctionButton( 
            string str) 
        { 
            bool success = false; 
            switch (str) 
            { 
               // The rest of code can be found  
               // in the downloaded source code 
            } 
            return success; 
        } 
    } 
} 

Creating the engine code in the functional approach

We have successfully created the calculator application in the imperative approach. Now, it's time to refactor all the imperative code into the functional code. We are going to refactor the engine first and then the code behind the form.

Adding several new properties

We will have exactly the same properties as with the imperative code, except that we add three new properties, as shown in the following code:

namespace CalculatorFunctional 
{ 
    public class Calc 
    { 
        public string m_input { get; set; } 
        public string m_sign { get; set; } 
        public string m_operator { get; set; } 
        public string m_lastNum { get; set; } 
        public string m_lastInput { get; set; } 
        public bool m_wait { get; set; } 
        public bool m_decimal { get; set; } 
        public bool m_lastHitEquals { get; set; } 
 
        public bool m_solve { get; set; } 
        public string m_answer { get; set; } 
        public bool m_funcSuccess { get; set; } 
    } 
} 

As you can see in the preceding code, m_solve, m_answer, and m_funcSuccess are the new properties we have just added. We will use these three additional properties in the Solve() function later.

Simplifying the pattern matching

As we discussed in Chapter 9, Working with Pattern, we will use the Simplicity class, which we can find in the SimplicityLib.cs file. The implementation of the class is as follows:

namespace CalculatorFunctional 
{ 
    public static class CalcMethodsExtension 
    { 
        public static Calc AppendNum( 
            this Calc calc, 
            double numValue) 
        {             
           // The rest of code can be found  
           // in the downloaded source code 
        } 
 
        public static Calc AppendNumWhenRound( 
            this Calc calc, 
            double numValue) 
        { 
           // The rest of code can be found  
           // in the downloaded source code 
        } 
         
        // The rest of code can be found  
        // in the downloaded source code  
    } 
} 

Assigning the properties

To able to assign properties, we need to assign the properties' extension method. The following code will explain this better:

namespace CalculatorFunctional 
{ 
    public static class CalcPropertiesExtension 
    { 
        public static Calc Input( 
            this Calc calc, 
            string input) 
        { 
            calc.m_input = 
                input; 
            return calc; 
        } 
 
        public static Calc LastNum( 
            this Calc calc, 
            string lastNum) 
        { 
            calc.m_lastNum = 
                lastNum; 
            return calc; 
        } 
          
        // The rest of code can be found  
        // in the downloaded source code 
 
        public static Calc ModifyCalcFuncSuccess( 
            this Calc calc, 
            bool val) 
        { 
            calc.m_funcSuccess = val; 
            return calc; 
        } 
 
        public static Calc ModifyCalcFuncSuccessBasedOn( 
            this Calc calc, 
            Func<bool> predicate) 
        { 
            return predicate() ? 
                calc.ModifyCalcFuncSuccess(true) : 
                calc.ModifyCalcFuncSuccess(false); 
        } 
    } 
} 

Every time we invoke one of the preceding methods, the method will return the Calc class in which the target property has been changed.

Constructing the class by clearing the properties

We will not construct the class in this functional approach; we will clear properties to make all the properties ready to run the process. There are two clearing methods that we will use: the Clear() and ClearAll() methods. The following code snippet is the implementation of these two methods:

namespace CalculatorFunctional 
{ 
    public static class CalcMethodsExtension 
    { 
        public static Calc Clear( 
            this Calc calc) 
        { 
            return calc 
                .ModifyCalcSign("+") 
                .ModifyCalcInput(".") 
                .ModifyCalcDecimal(false); 
        } 
 
        public static Calc ClearAll( 
            this Calc calc) 
        { 
            return calc 
                .Clear() 
                .ModifyCalcLastNum(null) 
                .ModifyCalcLastInput(null) 
                .ModifyCalcOperator(null) 
                .ModifyCalcWait(false) 
                .ModifyCalcLastHitEquals(false); 
        } 
    } 
} 

As we discussed in the imperative approach, the Clear() method is for the btnClearEntry button and ClearAll() is for the btnClearAll button.

Appending the inputted number to the text box

In this functional approach, we will refactor the AppendNum() method in the imperative approach into the functional approach, as shown in the following code:

namespace CalculatorFunctional 
{ 
    public static class CalcMethodsExtension 
    { 
        public static Calc AppendNum( 
            this Calc calc, 
            double numValue) 
        { 
            // The rest of code can be found  
            // in the downloaded source code 
        } 
 
        public static Calc AppendNumWhenRound( 
            this Calc calc, 
            double numValue) 
        { 
           // The rest of code can be found  
           // in the downloaded source code 
        } 
          
        // The rest of code can be found  
        // in the downloaded source code  
    } 
} 

Preparing the operation

To prepare the operation just after we press the operator button, here is the code that has been refactored from the PreparingOperation() method in the imperative approach:

namespace CalculatorFunctional 
{ 
    public static class CalcMethodsExtension 
    { 
        public static Calc PrepareOperation( 
            this Calc calc, 
            string strOperator) 
        { 
            // The rest of code can be found  
            // in the downloaded source code 
        } 
 
        public static Calc PrepareOperationAdd( 
            this Calc calc) 
        { 
            // The rest of code can be found  
            // in the downloaded source code 
        } 
 
        public static Calc  
            PrepareOperationAddLastNumNull( 
                this Calc calc) 
        { 
            // The rest of code can be found  
            // in the downloaded source code 
        } 
 
         
        // The rest of code can be found  
        // in the downloaded source code 
    } 
} 

Formatting the input

To format the input that we use to form the input in txtScreen, we will use the following code:

namespace CalculatorFunctional 
{ 
    public static class CalcMethodsExtension 
    { 
        public static String FormatInput( 
            this Calc calc,  
            String n) 
        { 
            return n 
                .ModifyStringWhen( 
                    () => n.IndexOf(".") == 0, 
                    () => n = "0" + n) 
                .ModifyStringWhen( 
                    () => n.IndexOf(".") == n.Length - 1, 
                    () => n = n + "0") 
                .ModifyStringWhen( 
                    () => calc.m_sign.Equals("-") && 
                        n != "0.0" && 
                        n.IndexOf("-") == -1, 
                    () => n = "-" + n); 
        } 
    } 
} 

As you can see in the preceding code, we use the ModifyStringWhen() extension method, which has the following implementation:

namespace CalculatorFunctional 
{ 
  public static class StringMethodsExtension 
  { 
    public static string ModifyStringWhen( 
      this string @this, 
      Func<bool> predicate, 
      Func<string> modifier) 
    { 
      return predicate() 
      ? modifier() 
      : @this; 
    } 
  } 
} 

Solving the calculation

The solving calculation can be done using the Solve() method in the imperative approach. The following code is the refactoring Solve() method from the imperative approach:

namespace CalculatorFunctional 
{ 
  public static class CalcMethodsExtension 
  { 
    public static Calc Solve( 
      this Calc calc) 
    { 
      return calc.CleanUp() 
      .Answer() 
      .UpdateAnswerToCalc(); 
    } 
  } 
} 

For the implementation of the CleanUp(), Answer(), and UpdateAnswerToCalc() methods, we can use the following code:

namespace CalculatorFunctional 
{ 
    public static class CalcSolveMethodsExtension 
    { 
        public static Calc Answer( 
            this Calc calc) 
        { 
            calc.m_answer = calc.m_operator.Match() 
                .With(o => o == "+",  
                    calc.m_lastNum.SolveAdd( 
                        calc.m_lastInput)) 
                .With(o => o == "-",  
                    calc.m_lastNum.SolveSubtract( 
                        calc.m_lastInput)) 
                .With(o => o == "*",  
                    calc.m_lastNum.SolveMultiply( 
                        calc.m_lastInput)) 
                .With(o => o == "/",  
                    !calc.FormatInput( 
                        calc.m_lastInput).Equals( 
                            "0.0") ?  
                        calc.m_lastNum.SolveDivide( 
                            calc.m_lastInput) :  
                        "") 
                .Else("") 
                .Do(); 
 
            calc.m_solve = calc.m_answer.Match() 
                .With(o => o.Equals(""), false) 
                .Else(true) 
                .Do(); 
 
            return calc; 
        } 
 
        public static Calc CleanUp( 
            this Calc calc) 
        { 
            return calc 
                .ModifyCalcInputWhen( 
                    () => calc.m_input.Equals(""), 
                    "0") 
                .ModifyCalcLastNumWhen( 
                    () => calc.m_lastNum == null || 
                        calc.m_lastNum.Equals(""), 
                    "0,0") 
                .ModifyCalcLastInputWhen( 
                    () => !calc.m_wait, 
                    "" + calc.FormatInput( 
                        calc.m_input)); 
        } 
 
        public static Calc UpdateAnswerToCalc( 
            this Calc calc) 
        { 
            calc.m_lastNum = calc.m_answer; 
            calc.m_input = calc.m_answer; 
            calc.m_sign = "+"; 
            calc.m_decimal = false; 
            calc.m_lastHitEquals = true; 
            calc.m_wait = true; 
 
            calc.m_solve = true; 
            return calc; 
        } 
    } 
} 

We also need to create the extension method for the string data type to accommodate the addition, subtraction, multiplication, and division operations, as follows:

namespace CalculatorFunctional 
{ 
    public static class StringMethodsExtension 
    { 
        public static string SolveAdd( 
            this string @string,  
            string str) 
        { 
            return Convert.ToString( 
                Convert.ToDouble(@string) + 
                Convert.ToDouble(str)); 
        } 
 
        public static string SolveSubtract( 
            this string @string, 
            string str) 
        { 
            return Convert.ToString( 
                Convert.ToDouble(@string) - 
                Convert.ToDouble(str)); 
        } 
 
        public static string SolveMultiply( 
            this string @string, 
            string str) 
        { 
            return Convert.ToString( 
                Convert.ToDouble(@string) * 
                Convert.ToDouble(str)); 
        } 
 
        public static string SolveDivide( 
            this string @string, 
            string str) 
        { 
            return Convert.ToString( 
                Convert.ToDouble(@string) / 
                Convert.ToDouble(str)); 
        } 
    } 
} 

Calculating the additional operation

For the additional button, which will invoke the FunctionButton() method every time the additional button is pressed, here is the refactoring code from the imperative FunctionButton() method:

namespace CalculatorFunctional 
{ 
    public static class CalcMethodsExtension 
    { 
        public static Calc PrepareOperation( 
            this Calc calc, 
            string strOperator) 
        { 
            // The rest of code can be found  
            // in the downloaded source code 
        } 
 
        public static Calc PrepareOperationAdd( 
            this Calc calc) 
        { 
            // The rest of code can be found  
            // in the downloaded source code 
        } 
  
        // The rest of code can be found  
        // in the downloaded source code 
    } 
} 
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset