📜 ⬆️ ⬇️

What prepares us for C # 7 (Part 2. Pattern matching)

Continuing a series of articles on innovations in C # 7, I focus on, perhaps, the main innovations - Pattern matching and Record type (Approximate translation "registered types"). These functionals complement each other, so it’s best to talk about them together.

Let's start with the Record type. He comes to us from F #. In essence, this is a quick definition of a class, and with the impossibility of changing its properties, i.e. all its fields have a readonly parameter, and parameters are set in the constructor. Describing it for a long time and tedious, so we start immediately with a sample code and by example we will sort everything out. Here is an example of the definition of a record type:

public class Cartesian(double x: X, double y: Y); 


This is the definition of some class that stores the Cartesian coordinates of a point. It should be broadcast in this class:

 public class Cartesian { private readonly double $X; private readonly double $Y; public Cartesian(double x, double y) { this.$X = x; this.$Y = y; } public double X { get { return this.$X; } } public double Y { get { return this.$Y; } } public static bool operator is(Cartesian c, out double x, out double y) { x = cX; y = cY; return true; } override public bool Equals(object obj) { if (obj.GetType() != typeof(Cartesian)) return false; var $o = obj as Cartesian; return object.Equals(X, $oX) && object.Equals(Y, $oY); } override public int GetHashCode() { int $v = 1203787; $v = ($v * 28341) + X?.GetHashCode().GetValueOrDefault(); $v = ($v * 28341) + Y?.GetHashCode().GetValueOrDefault(); } override public string ToString() { return new System.Text.StringBuilder() .Append(“Cartesian(X: “) .Append(X) .Append(“, Y: ”) .Append(Y) .Append(“)”) .ToString(); } } 

Let us examine the properties of the class. In its definition, we specified two double parameters. These parameters are translated into two read-only properties, and two read-only fields within the class. Then a constructor is created with the parameters specified in the class definition. Also, methods Equals , GetHashCode , ToString are created .
')
Of most interest is the overloaded is operator. Here it is just more related to Pattern matching. The is operator now supports an additional comparison, in addition to the usual type-casting test. It is also possible to additionally call this overloaded operator on the class. To begin with, how the operator is overloaded and what actions can be performed. The first parameter in the operator is the class object passed to it, it does not have to be the class of this operator. Then come the return parameters with which we need to compare or which should be obtained when executing the is operator. When creating a class through a record type, an is operator is created with the class passed to the record type and the return values ​​of this class specified in the definition. Here is an example of how to convert Cartesian coordinates to polar coordinates using the is operator:

 public static class Polar { public static bool operator is(Cartesian c, out double R, out double Theta) { R = Math.Sqrt(cX*cX + cY*cY); Theta = Math.Atan2(cY, cX); return cX != 0 || cY != 0; } } 

What we get if we pass an object of the Cartesian class to the operator: it will try to convert the data of this class to the data of the Polar class and return the transformed data.

Pattern matching (or Pattern Matching; although I don’t really like the name, the English definition seems more accurate), what is it? He came to us from languages ​​like Python and F #. In essence, this is an advanced switch, which not only can compare values ​​of one type with constants, but also use type conversion and their conversion to the required structure. And in all of this, the new overloaded is operator will help us. Let's start with the new features of the old type-checking operator. Now instead of this:

 var v = expr as Type; if (v != null) { //  v } 

You can write like this:

 if (expr is Type v) { //  v } 

This, of course, shortens the type conversion code. But let's return to Pattern matching and find out what opportunities it is preparing for us. Let us write a check to reduce the specific Cartesian coordinates to the polar coordinates and obtain the radius:

 var c = Cartesian(3, 4); if (c is Polar(var R, *)) Console.WriteLine(R); 

So, what happens here, let's figure it out. The variable c is taken, the type of the variable is obtained, and the operator is is searched for, where the first parameter is this type. Then this statement is called and, if it returns true, the condition is considered to be fulfilled. Next, we get the local variable R in the condition block. Here the angle is not important to us, and therefore we passed the second parameter * - this means ignoring the second parameter. Another possible use of this operator:

 if (c is Polar(5, *)) Console.WriteLine("  5"); 

Here we impose an additional condition on the return value of the radius, and the condition is fulfilled only when the radius is 5.

The main use of the new is operator is , of course, in the switch statement . We give an example of solving algebraic expressions using pattern matching. We define the classes we need using the record type.

 abstract class Expr; class X() : Expr; class Const(double Value) : Expr; class Add(Expr Left, Expr Right) : Expr; class Mult(Expr Left, Expr Right) : Expr; class Neg(Expr Value) : Expr; 

First, we write a method for taking the derivative:

 Expr Deriv(Expr e) { switch (e) { case X(): return Const(1); case Const(*): return Const(0); case Add(var Left, var Right): return Add(Deriv(Left), Deriv(Right)); case Mult(var Left, var Right): return Add(Mult(Deriv(Left), Right), Mult(Left, Deriv(Right))); case Neg(var Value): return Neg(Deriv(Value)); } } 

Or a simplified expression:

 Expr Simplify(Expr e) { switch (e) { case Mult(Const(0), *): return Const(0); case Mult(*, Const(0)): return Const(0); case Mult(Const(1), var x): return Simplify(x); case Mult(var x, Const(1)): return Simplify(x); case Mult(Const(var l), Const(var r)): return Const(l*r); case Add(Const(0), var x): return Simplify(x); case Add(var x, Const(0)): return Simplify(x); case Add(Const(var l), Const(var r)): return Const(l+r); case Neg(Const(var k)): return Const(-k); default: return e; } } 

In the descriptions of this functional, I met mainly examples related to mathematical calculations. I will be very happy to see in the comments your examples where this functionality will really be useful not in mathematical calculations.

Here you can read the source

Source: https://habr.com/ru/post/257283/


All Articles