Chapter 9. Working with Pattern

In the previous chapter, we discussed optimizing code to develop efficient code. Now, we are going to discuss the pattern that will make our code flow in a regular order so that it will be easier to maintain and understand the flow of the program. The main topics we are going to discuss in this chapter are pattern matching and Monad as a design pattern. Pattern matching will match the condition using a mathematical approach so that we will get a functional taste of things. And Monad is an inseparable part of the functional programming since it's a design pattern for complex problems in software design. Using Monad, we can give more power to existing data types by amplifying their behavior. This chapter will look further into pattern matching and Monad, and we will discuss the following topics:

  • Understanding pattern matching in functional programming
  • Transforming data and switching decisions using pattern matching
  • Simplifying pattern matching in order to make it more functional
  • Testing pattering matching feature in C# 7
  • Finding out which C# types implement Monad naturally
  • Generating monadic types
  • Understanding the rules of Monad

Dissecting pattern matching in functional programming

In functional programming, pattern matching is a form of dispatch to choose the correct variant of the functions to be called. It's actually inspired by a standard mathematical notation with the syntax to express conditional execution. We can start our discussion on matching pattern by borrowing the code from Chapter 1, Tasting Functional Style in C#, when we talked about recursion. The following is the GetFactorial() functional we used to retrieve a factorial value:

public partial class Program 
{ 
  private static intGetFactorial(intintNumber) 
  { 
    if (intNumber == 0) 
    { 
      return 1; 
    } 
    returnintNumber * GetFactorial(intNumber - 1); 
  } 
} 

As we can see in the preceding code, it gives us two definitions. In this case, the dispatcher is chosen based on whether the actual intNumber parameter pattern matches 0 or not. The use of the pattern matching are closer to this if conditional expression since we have to decide which section will be selected by providing a specific input.

Transforming data using pattern matching

Pattern matching is somehow transforming data. Let's borrow another function from the previous chapter to continue the discussion. As we might remember, we had a function in the extension method called IsPrime() to check whether or not it is a prime number. We will use it again to demonstrate pattern matching to transform data. For those who have forgotten the implementation of the IsPrime() function, here is the code:

public static class ExtensionMethods 
{ 
  public static bool IsPrime(this int i) 
  { 
    if ((i % 2) == 0) 
    { 
      return i == 2; 
    } 
    int sqrt = (int)Math.Sqrt(i); 
    for (int t = 3; t <= sqrt; t = t + 2) 
    { 
      if (i % t == 0) 
      { 
        return false; 
      } 
    } 
    return i != 1; 
  } 
} 

Again, we use pattern matching to determine whether the number is a prime number, composite number, or neither. However, now we will transform the int number into text, as we can see in the following NumberFactorType() function, which we can find in the MatchingPattern.csproj project:

public partial class Program 
{ 
  public static string NumberFactorType( 
    int intSelectedNumber) 
  { 
    if (intSelectedNumber < 2) 
    { 
      return "neither prime nor composite number"; 
    } 
    else if (intSelectedNumber.IsPrime()) 
    { 
      return "prime number"; 
    } 
    else 
    { 
      return "composite number"; 
    } 
  } 
} 

As we can see in the preceding code, we use the if...else conditional statement to match the condition instead of the if conditional statement we used in the previous example. Now, let's call the NumberFactorType() function to match the int number we give and transform it into text using the following TransformIntIntoText() function:

public partial class Program 
{ 
  public static void TransformIntIntoText() 
  { 
    for (int i = 0; i < 10; i++) 
    { 
      Console.WriteLine( 
        "{0} is {1}", i, NumberFactorType(i)); 
    } 
  } 
} 

We pass the number 0 to 9 into the NumberFactorType() function to get a matching result. And if we run the TransformIntIntoText() function, we get the following output on the console:

Transforming data using pattern matching

As you can see from the preceding screenshot, we have successfully used pattern matching to transform int into text.

Switching for pattern matching

We know that pattern matching can transform data into another form. This is actually similar to the Select() method in LINQ and conceptually similar to the switch case statement. Now let's take a look at the following HexCharToByte() function to convert a hexadecimal character into byte:

public partial class Program 
{ 
  public static byte HexCharToByte( 
    char c) 
  { 
    byte res; 
 
    switch (c) 
    { 
      case '1': 
        res = 1; 
        break; 
      case '2': 
        res = 2; 
        break; 
      case '3': 
        res = 3; 
        break; 
      case '4': 
        res = 4; 
        break; 
      case '5': 
        res = 5; 
        break; 
      case '6': 
        res = 6; 
        break; 
      case '7': 
        res = 7; 
        break; 
      case '8': 
        res = 8; 
        break; 
      case '9': 
        res = 9; 
        break; 
      case 'A': 
      case 'a': 
        res = 10; 
        break; 
      case 'B': 
      case 'b': 
        res = 11; 
        break; 
      case 'C': 
      case 'c': 
        res = 12; 
        break; 
      case 'D': 
      case 'd': 
        res = 13; 
        break; 
      case 'E': 
      case 'e': 
        res = 14; 
        break; 
      case 'F': 
      case 'f': 
        res = 15; 
        break; 
      default: 
        res = 0; 
        break; 
    } 
 
    return res; 
  } 
} 

Then, we add a wrapper to convert the hexadecimal in string into int, as shown in the following HexStringToInt() function:

public partial class Program 
{ 
  public static intHexStringToInt( 
    string s) 
  { 
    int iCnt = 0; 
    int retVal = 0; 
    for (inti = s.Length - 1; i>= 0; i--) 
    { 
      retVal += HexCharToByte(s[i]) *  
        (int) Math.Pow(0x10, iCnt++); 
    } 
    return retVal; 
  } 
} 

From the preceding code, we can see that we call the HexCharToByte() function to get each int value for each hexadecimal character. Then, we use the power of 16 to get all the hexadecimal values. Suppose we have the following GetIntFromHexString() function to convert several hexadecimal digits in a string into int:

public partial class Program 
{ 
  private static void GetIntFromHexString() 
  { 
    string[] hexStrings = { 
      "FF", "12CE", "F0A0", "3BD", 
      "D43", "35", "0", "652F", 
      "8DCC", "4125" 
    }; 
    for (int i = 0; i < hexStrings.Length; i++) 
    { 
      Console.WriteLine( 
        "0x{0}	= {1}", 
        hexStrings[i], 
        HexStringToInt(hexStrings[i])); 
    } 
  } 
} 

If we run the GetIntFromHexString() function, we get the following output on the console:

Switching for pattern matching

As you can see in the preceding screenshot, each hexadecimal character in the string is converted into the int value and then it sums up all the results.

Tip

To convert a hexadecimal character into a byte, we can use the Parse and TryParse methods or format it using the String.Format. HexCharToByte() function we discussed earlier, which is for sample purposes only.

Simplifying pattern matching

We have successfully used the switch statement to implement pattern matching. However, the example doesn't apply a functional approach since the res variable in the HexCharToByte() function is mutated during execution. Now, we are going to refactor the HexCharToByte() function in order to apply a functional approach. Let's take a look at the following HexCharToByteFunctional() function, which can be found in the SimplifyingPatternMatching.csproj project:

public partial class Program 
{ 
  public static byte HexCharToByteFunctional( 
    char c) 
  { 
    return c.Match() 
      .With(ch =>ch == '1', (byte)1) 
      .With(ch =>ch == '2', 2) 
      .With(ch =>ch == '3', 3) 
      .With(ch =>ch == '4', 4) 
      .With(ch =>ch == '5', 5) 
      .With(ch =>ch == '6', 6) 
      .With(ch =>ch == '7', 7) 
      .With(ch =>ch == '8', 8) 
      .With(ch =>ch == '9', 9) 
      .With(ch =>ch == 'A', 10) 
      .With(ch =>ch == 'a', 10) 
      .With(ch =>ch == 'B', 11) 
      .With(ch =>ch == 'b', 11) 
      .With(ch =>ch == 'C', 12) 
      .With(ch =>ch == 'c', 12) 
      .With(ch =>ch == 'D', 13) 
      .With(ch =>ch == 'd', 13) 
      .With(ch =>ch == 'E', 14) 
      .With(ch =>ch == 'e', 14) 
      .With(ch =>ch == 'F', 15) 
      .With(ch =>ch == 'f', 15) 
      .Else(0) 
      .Do(); 
  } 
} 

The preceding HexCharToByteFunctional() function is refactored from the HexCharToByte() function and now implements the functional approach. As you can see, we have four methods similar to the switch statement or the if...else condition statement: Match(), With(), Else(), and Do(). Let's take a look at the following Match() function used by the preceding HexCharToByteFunctional() function:

public static class PatternMatch 
{ 
  public static PatternMatchContext<TIn> Match<TIn>( 
    this TIn value) 
  { 
    return new PatternMatchContext<TIn>(value); 
  } 
} 

As you can see, the Match() function returns the new PatternMatchContext data type. The PatternMatchContext class implementation is as follows:

public class PatternMatchContext<TIn> 
{ 
  private readonlyTIn _value; 
  internal PatternMatchContext(TIn value) 
  { 
    _value = value; 
  } 
 
  public PatternMatchOnValue<TIn, TOut> With<TOut>( 
    Predicate<TIn> condition,  
    TOut result) 
  { 
    return new PatternMatchOnValue<TIn, TOut>(_value) 
      .With(condition, result); 
  } 
} 

When the Match() function generates a new instance of PatternMatchContext, its constructor stores the value passed as an argument to the _value private variable, as shown in the following code snippet:

internal PatternMatchContext(TIn value) 
{ 
  _value = value; 
} 

In this PatternMatchContext class, there is also a method called With(), which we can compare with the _value value. The method will invoke the With() method inside the PatternMatchOnValue class, the implementation of which is as follows:

public class PatternMatchOnValue<TIn, TOut> 
{ 
  private readonlyIList<PatternMatchCase> _cases =  
    new List<PatternMatchCase>(); 
  private readonlyTIn _value; 
  private Func<TIn, TOut> _elseCase; 
 
  internal PatternMatchOnValue(TIn value) 
  { 
    _value = value; 
  } 
 
  public PatternMatchOnValue<TIn, TOut> With( 
    Predicate<TIn> condition,  
    Func<TIn, TOut> result) 
  { 
    _cases.Add(new PatternMatchCase 
    { 
      Condition = condition, 
      Result = result 
    }); 
 
    return this; 
  } 
 
  public PatternMatchOnValue<TIn, TOut> With( 
    Predicate<TIn> condition,  
    TOut result) 
  { 
    return With(condition, x => result); 
  } 
 
  public PatternMatchOnValue<TIn, TOut> Else( 
  Func<TIn, TOut> result) 
  { 
    if (_elseCase != null) 
    { 
      throw new InvalidOperationException( 
        "Cannot have multiple else cases"); 
    } 
    _elseCase = result; 
    return this; 
  } 
 
  public PatternMatchOnValue<TIn, TOut> Else( 
    TOut result) 
  { 
    return Else(x => result); 
  } 
 
  public TOut Do() 
  { 
    if (_elseCase != null) 
    { 
      With(x => true, _elseCase); 
      _elseCase = null; 
    } 
 
    `foreach (var test in _cases) 
    { 
      if (test.Condition(_value)) 
      { 
        returntest.Result(_value); 
      } 
    } 
 
    throw new IncompletePatternMatchException(); 
  } 
 
  private structPatternMatchCase 
  { 
    public Predicate<TIn> Condition; 
    publicFunc<TIn, TOut> Result; 
  } 
} 

As you can see from the preceding code, when the With() method, which is a member of the PatternMatchContext class, returns a new instance of PatternMatchOnValue, its constructor also stores the value to the _value private variable, as shown in the following code snippet:

internal PatternMatchOnValue(TIn value) 
{ 
  _value = value; 
} 

It then calls the With() method, which is passed an anonymous method as condition and an expected value as result, as shown in the following code snippet:

public PatternMatchOnValue<TIn, TOut> With( 
  Predicate<TIn> condition, 
  TOut result) 
{ 
  return With(condition, x => result); 
} 

This With() method then calls another With() method, which passes Predicate<T> and Func<T1, T2> as shown in the following code snippet:

public PatternMatchOnValue<TIn, TOut> With( 
  Predicate<TIn> condition, 
  Func<TIn, TOut> result) 
{ 
  _cases.Add(new PatternMatchCase 
  { 
    Condition = condition, 
    Result = result 
  }); 
 
  return this; 
} 

This With() method collects all cases and stores them in the _cases list typed as PatternMatchCase, the implementation of which is as follows:

private structPatternMatchCase 
{ 
  public Predicate<TIn> Condition; 
  publicFunc<TIn, TOut> Result; 
} 

Once we have provided all the conditions, we call the Else() method, which contains the default result. The implementation of the Else() method is as shown in the following code snippet:

public PatternMatchOnValue<TIn, TOut> Else( 
  TOut result) 
{ 
  return Else(x => result); 
} 

We then invoke another Else() method passing Func<T1, T2>, as shown in the following code snippet:

public PatternMatchOnValue<TIn, TOut> Else( 
  Func<TIn, TOut> result) 
{ 
  if (_elseCase != null) 
  { 
    throw new InvalidOperationException( 
      "Cannot have multiple else cases"); 
  } 
  _elseCase = result; 
  return this; 
} 

After we collect all _cases and _elseCase variables, we have to invoke the Do() method to compare all cases. The implementation of the Do() method can be seen in the following code snippet:

public TOut Do() 
{ 
  if (_elseCase != null) 
  { 
    With(x => true, _elseCase); 
    _elseCase = null; 
  } 
  foreach (var test in _cases) 
  { 
    if (test.Condition(_value)) 
    { 
      returntest.Result(_value); 
    } 
  } 
  throw new IncompletePatternMatchException(); 
} 

As you can see, the Do() method will assign the _elseCase variable, if any, to the _cases list using the With() method, as shown in the following code snippet:

if (_elseCase != null) 
{ 
  With(x => true, _elseCase); 
  _elseCase = null; 
} 

It then compares all _cases list members using the foreach loop to find the correct _value value using the following code snippet:

foreach (var test in _cases) 
{ 
  if (test.Condition(_value)) 
  { 
    return test.Result(_value); 
  } 
} 

Although invoking the Else() method is optional, it's mandatory to match one of all the With() method invocations. If not, the Do() method will throw an IncompletePatternMatchException exception, as shown in the following code snippet:

throw new IncompletePatternMatchException(); 

For now, we don't need to implement anything in the IncompletePatternMatchException exception, so we just need to create a new class implementation Exception class, as shown in the following code:

public class IncompletePatternMatchException : 
  Exception 
{ 
} 

Until here, we have successfully refactored the HexCharToByte() function into the HexCharToByteFunctional() function. We can modify the HexStringToInt() function to invoke the HexCharToByteFunctional() function, as shown in the following code:

public partial class Program 
{ 
  public static intHexStringToInt( 
    string s) 
  { 
    int iCnt = 0; 
    int retVal = 0; 
    for (int i = s.Length - 1; i >= 0; i--) 
    { 
      retVal += HexCharToByteFunctional(s[i]) * 
      (int)Math.Pow(0x10, iCnt++); 
    } 
 
    return retVal; 
  } 
} 

However, the HexStringToInt() function does not implement the functional approach. We can refactor it to the HexStringToIntFunctional() function, as shown in the following code:

public partial class Program 
{ 
  public static intHexStringToIntFunctional( 
    string s) 
  { 
    returns.ToCharArray() 
     .ToList() 
     .Select((c, i) => new { c, i }) 
     .Sum((v) => 
       HexCharToByteFunctional(v.c) * 
         (int)Math.Pow(0x10, v.i)); 
  } 
} 

From the preceding HexStringToIntFunctional() function, we can see that, first, we convert the strings into a list of characters by reversing the order of the list. This is because we need to assign the least significant byte with the lowest index. We then select each member of the list and create a new class that contains the character itself and the index. Afterwards, we sum them up based on their index and value. Now, we have the following GetIntFromHexStringFunctional() function, and it invokes the HexStringToIntFunctional() function:

public partial class Program 
{ 
  private static void GetIntFromHexStringFunctional() 
  { 
    string[] hexStrings = { 
      "FF", "12CE", "F0A0", "3BD", 
      "D43", "35", "0", "652F", 
      "8DCC", "4125" 
    }; 
    Console.WriteLine( 
      "Invoking GetIntFromHexStringFunctional() function"); 
    for (int i = 0; i<hexStrings.Length; i++) 
    { 
      Console.WriteLine( 
        "0x{0}	= {1}", 
        hexStrings[i], 
        HexStringToIntFunctional( 
          hexStrings[i])); 
    } 
  } 
} 

This is actually similar to the GetIntFromHexString() function in the MatchingPattern.csproj project. If we run the GetIntFromHexStringFunctional() function, we will get the following output on the console:

Simplifying pattern matching

As you can see, we get the exact same output compared to the GetIntFromHexString() function in the MatchingPattern.csproj project since we have successfully refactored it to functional pattern matching.

Note

For a simpler method in pattern matching, we can use the Simplicity NuGet package, which we can download directly from Visual Studio using Package Manager Console and typing Install-PackageSimplicity.

Welcoming the coming of pattern matching feature in C# 7

The planned language features in C# 7 includes pattern matching which has the extensions to the is operator. We now can introduce a new variable after the type and this variable is assigned to the left-hand side operand of the is operator but with the type specified as the right-hand side operand. Let's make it clear using the following code snippet which we can find in MatchingPatternCSharp7.csproj project:

public partial class Program 
{ 
  private static void IsOperatorBeforeCSharp7() 
  { 
    object o = GetData(); 
    if (o is String) 
    { 
      var s = (String)o; 
      Console.WriteLine( 
        "The object is String. Value = {0}", 
          s); 
    } 
  } 
} 

And the GetData() function implementation is as follow:

public partial class Program 
{ 
  private static object GetData( 
      bool objectType = true) 
  { 
    if (objectType) 
        return "One"; 
    else 
        return 1; 
  } 
} 

In the preceding IsOperatorBeforeCSharp7() function, we should assign s variable with the value of o after we check the content of the o object variable in if statement. It is what we can do before C# 7 introduce pattern matching feature. Now, let's compare the preceding code with the following IsOperatorInCSharp7() function:

public partial class Program 
{ 
  private static void IsOperatorInCSharp7() 
  { 
    object o = GetData(); 
    if (o is String s) 
    { 
      Console.WriteLine( 
          "The object is String. Value = {0}", 
           s); 
    } 
  } 
} 

As we can see, we now can assign s variable with the content of o variable but with the string data type, as we have discussed earlier. We assign the s variable inside the if statement when the condition is checked.

Fortunately, this feature can also be applied in switch statement as we can see in the following code snippet:

public partial class Program 
{ 
  private static void SwitchCaseInCSharp7() 
  { 
    object x = GetData( 
        false); 
    switch (x) 
    { 
      case string s: 
          Console.WriteLine( 
              "{0} is a string of length {1}", 
              x, 
              s.Length); 
          break; 
      case int i: 
          Console.WriteLine( 
              "{0} is an {1} int", 
              x, 
              (i % 2 == 0 ? "even" : "odd")); 
          break; 
      default: 
          Console.WriteLine( 
              "{0} is something else", 
              x); 
          break; 
    } 
  } 
} 

As we can see in the preceding SwitchCaseInCSharp7() function, we can assign s and i variable with the content of x variable in the case checking so we don't need to assign the variable again.

Note

For more information about pattern matching feature in C# 7, we can find it in official Roslyn GitHub page on https://github.com/dotnet/roslyn/blob/features/patterns/docs/features/patterns.md

..................Content has been hidden....................

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