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:
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:
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.
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, "-"); }
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.
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.
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.
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; } } }
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;
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; }
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.
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; } } }
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; } } }
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.
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.
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 } }
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.
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.
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 } }
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 } }
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; } } }
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)); } } }
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 } }