Scala's pattern matching and conditional classes
A tree is a data structure that is commonly used in programs. For example, compilers and parsers are often represented as trees, XML document structures are tree-like, and some are tree-based, such as red-black trees.
Next we will use a calculator program to study how the tree is represented and manipulated in Scala. The goal of this program is to handle some simple arithmetic expressions consisting of integer constants, variables, and plus signs, such as 1 + 2 and (x + x) + (7 + y).
We will first decide how to represent these expressions. The most natural approach is the tree, where the nodes of the tree represent the operators (only additions are here), and the tree's leaf nodes represent values (constants and variables are represented here). In Java, such a tree can be represented as a collection of a super-class tree, and the nodes are represented by instances of different subclasses. In functional languages, we can use algebraic types (algebraic data-type) to achieve the same purpose. Scala provides a kind of thing called conditional class (case Classes) between the two.
Abstract class Tree Case class Sum (L:tree, r:tree) extends the tree case class Var (n:string) extends the tree case class Const ( V:int) extends Tree
We have actually defined three conditional classes Sum, Var, and Const. These classes differ from ordinary classes in a number of ways:
1. You can omit the New keyword when instantiating (for example, you can use const (5) without using the new const (5))
2. The getter function of the parameter is automatically defined (for example, you can access the class const instance by C.V C when instantiating the parameter V)
3. Has the default predefined equals and Hashcode implementations, which can differentiate between class instances by value, rather than using them.
4. Have the default ToString implementation. The implementation of the code that implements the return value (for example, an expression x+1 can be expressed as sum (Var (x), Const (1)))
5. Instances of the condition class can be analyzed by pattern matching, and we'll talk about this feature next.
Now that we have defined the data types that represent our arithmetic expressions, we can begin to define the corresponding actions for them. We will first write a function that evaluates the expression in the context. The context here refers to a binding relationship between a variable and a value. For example, the expression x+1 in the x=5 context should result in 6.
So we need to find a way to represent this binding relationship. Of course we can use some kind of data structure like hash-table, but we can also use the function directly! A context is nothing more than a bar name mapping to a value function. For example, this mapping of {x→5} given above can be expressed in Scala as:
{case "X" = 5} This defines a function: Returns an integer 5 if the argument equals the string "X", or throws an exception.
Before we write the evaluation function, we need to give our context a name so that it can be referenced in the following code. We've certainly used type string=>int, but if we give this type a name, it will make the program easier to read and easier to maintain. In Scala, this can be done with the following code:
Type environment = String = = Int
From now on, type environment is used as a function type name of string to int.
Now we can start defining the evaluation function. Conceptually, this is a very simple process: the sum of two expressions equals two expressions evaluated and then summed; the value of a variable can be extracted from the context, and the value of the constant is himself. There's no difficulty in Scala expressing this:
def eval (T:tree, env:environment): Int = t match {case Sum (l, r) = eval (l, Env) + eval (r, Env) case Var (n) + en V (n) case Const (v) = v}
The evaluation function accomplishes the work by pattern matching the tree T. Intuitively, the idea of the above code is very clear:
1. The first pattern checks to see if the root node of the incoming tree is a sum, and if it does, it will assign the tree's left subtree to L, the subtree on the right to R, and then follow the code following the arrows, which can (and does) use variables that are bound to match on the left, such as L and R here.
2. If the first check does not succeed, indicating that the incoming tree is not sum, the program continues to check whether he is a Var, and if so, the variable name is assigned to N and continues to the right.
3. If the second check also fails, indicating that T is neither sum nor VAR, the program checks that he is not a const. If it is an assignment variable and continues.
4. Finally, if all the checks fail. Throws an exception to indicate that the pattern match failed. This can only happen if the tree's other definitions are defined.
We can see that the basic idea of pattern matching is to try to match a value in multiple patterns, split the matching value into several children at the same time, and then execute some code on the matching value with its children.
A skilled object-oriented programmer might want to know why we don't. Eval is defined as a tree or a member function such as it. We can actually do this. Because Scala allows conditional classes to define members like normal classes. Deciding whether to use pattern matching or member functions depends on the programmer's preferences, but this tradeoff is also important for extensibility:
1. When you use member functions, you can easily add new node types by inheriting the tree, but on the other hand, adding new operations is a chore because you have to modify all the subclasses of the tree.
2. When you use pattern matching yes, the situation just reverses, adding a new node type requires you to modify all of the functions that use pattern matching on the tree, but on the other hand, adding a new operation requires adding a pattern matching function.
Let's take a closer look at pattern matching, so we'll define an action for the expression: derivative of the symbol. Readers may want to first remember some of the following rules about this operation:
1. And the derivative equals the sum of the derivative,
2. If the symbol, etc. to guide the symbol, the derivative is 1, otherwise 0.
3. The derivative of the parameter is always 0.
The above rules can be translated directly into Scala code:
def derive (T:tree, v:string): Tree = t match {case Sum (l, r) = SUM (Derive (L, V), derive (R, v)) case Var (n) if (v = = N) = const (1) Case _ = = Const (0)}
This function uses two functions for pattern matching, the first case statement can have a guard clause: an IF condition expression. This mode will not match successfully unless the guard condition is set. Next is the wildcard: _. This pattern indicates that it matches all values and does not assign values to any variables.
In fact we are far from touching the full essence of pattern matching. But we are confined to the length of reason to stop the pen. Let's take a look at how the two functions run on an instance. To achieve this, we have written a simple main function to perform several operations on an expression (x + x) + (7 + y): First, the value of the expression when {x→5, y→7} is evaluated, and then the X and Y are respectively derivative.
def main (Args:array[string]) {val exp:tree = sum (sum (VAR ("x"), Var ("X")), Sum (Const (7), Var ("Y"))) Val env:environment = {case "X" = 5 case "Y" = 7} println ("Expression:" + exp) println ("Evaluation with X=5, y=7:" + eval (exp, env)) println ("derivative relative to x:\n" + Derive (exp, "x")) println ("derivative relative to y:\n" + Derive (exp, "Y"))}
Execute the program, we can get the following output:
Expression:sum (SUM (VAR (x), VAR (x)), Sum (Const (7), Var (y))) Evaluation with x=5, y=7:24 derivative relative to x:
SUM (SUM (const (1), const (1)), SUM (const (0), const (0))) derivative relative to Y:sum (sum (const (0), const (0)), sum (const (0 ), Const (1)))
By studying the program output, we can see that the output of the derivative can be simplified before it is printed, and it is interesting to use pattern matching to define a simplified function (but it also requires some skill) to work. Readers can try to complete the function themselves.
Read more about the great content: http://bbs.superwu.cn
Focus on the two-dimensional code of Superman Academy:
Scala's pattern matching and conditional classes