Scala Tutorial: Condition class and pattern matching

Source: Internet
Author: User
Tags scala tutorial

Previous Article: Scala Tutorial: interaction with Java, everything is an object

This document is translated from the official Scala tutorial. to prevent misunderstanding, keep the original document for reference.

Due to space limitations, there are three articles.

Blog formats are not easy to adjust, interested friends can download PDF version: http://download.csdn.net/source/1742639

---

6Condition class and pattern matching -Case classes and pattern matching

A tree is a frequently used data structure in software development. For example, the interpreter and compiler use a tree to represent the code structure. XML documents use a tree structure, and some containers (collections, containers) is also based on the tree, for example: red-black tree (a self-balancing Binary Search Tree ).

A kind of data structure that often appears in programs is the tree. for example, interpreters and compilers usually represent programs internally as trees; XML parameters are trees; and several kinds of containers are based on trees, like red-black trees.

Next, we will use an example program to understand how to represent and operate the tree structure in scala. This example will implement a very simple calculator function, this calculator can process arithmetic expressions that contain addition, variables, and Integer constants, such as: 1 + 2 ,(X+X) + (7 +Y.

We will now examine how such trees are represented and manipulated in Scala through a small calculator program. the aim of this program is to manipulate very simple arithmetic expressions composed of sums, Integer constants and variables. two examples of such expressions are 1 + 2 and (X+X) + (7 +Y).

First, we need to determine how to express this expression. The most natural choice is the tree structure, which uses non-leaf nodes to represent operators (in this example, only addition operations) and leaf nodes to represent operands (in this example, constants and variables ).

We recommend rst have to decide on a representation for such expressions. the most natural one is the tree, where nodes are operations (here, the addition) and leaves are values (here constants or variables ).

In Java, the most common way to create a tree structure is to create an abstract class that represents the tree, and then each type of node is represented by a subclass inherited from the abstract class. In functional programming languages, you can use the algebraic data-type to achieve the same purpose. Scala provides a concept between the two (class inheritance and algebraic data type), known as the condition class (Case classes). below is the sample code for defining a tree using the condition class:

In Java, such a tree wocould be represented using an abstract super-class for the trees, and one concrete sub-class per node or leaf. in a functional programming language, one wocould use an algebraic data-type for the same purpose. scala provides the conceptCase classesWhich is somewhat in between the two. Here is how they can be used to de facto ne the type of the trees for our example:

Abstract classTree

Case classSum (L: Tree, R: Tree)ExtendsTree

Case classVaR (N: string)ExtendsTree

Case classConst (V: INT)ExtendsTree

In the preceding example, sum, VAR, and const are condition classes. The differences between them and common classes are mainly reflected in the following aspects:

The fact that classes sum, VaR and const are declared as case classes means that they differ from standard classes in several respects:

· Create a condition class instance without usingNewKeyword (for example, you can directly use const (5) insteadNewConst (5) to create an instance ).

·NewKeyword is not mandatory to create instances of these classes (I. e. One can write const (5) insteadNewConst (5 )),

· Automatically create the corresponding getter method for the parameters included in the const (that is, if C is a const instance, you can access the value of the same name parameter V in the constructor through C. V)

· Getter functions are automatically de specified Ned for the constructor parameters (I. e. it is possible to get the value of the V constructor parameter of some instance c of class const just by writing C. V ),

· Both condition classes implement the equals and hashcode methods by default, but both methods are based on the structure of the Instance itself (structure of instance), rather than based on the identity that can be used to differentiate values in the instance ), this is basically the same as the default implementation of methods with the same name provided by objects in Java.

· Default de provided Nitions for methods equals and hashcode are provided, which work onStructureOf the instances and not on their identity,

· The condition class also provides a default tostring method that can print instance values (for example, expressions) in source form (source form ).X+ 1 is printed as sum (VAR (x), const (1). The printed result is exactly the same as the code used to create the Expression Tree in the source code ).

· A default de into nition for method tostring is provided, and prints the value in a "source form" (e.g. The tree for expressionX+ 1 prints as sum (VAR (x), const (1 ))),

· Instances of the condition class can be decomposed by pattern matching (decompose), which will be detailed later.

· Instances of these classes can be decomposed throughPattern MatchingAs we will see below.

Now that we have defined the data structure used to represent arithmetic expressions, we can then define the operations that work on these data structures. First, we define a function that evaluates the expression in a specific environment (environment, context). The environment is used to determine the value of the variable in the expression. For example, there is an environment where the value of variable X is 5, which is recorded :{X→ 5}, then, in this environmentX+ 1 value. The result is 6.

Now that we have de specified Ned the data-type to represent our arithmetic expressions, we can start de specified Ning operations to manipulate them. We will start with a function to evaluate an expression in someEnvironment. The aim of the environment is to give values to variables. For example, the expressionX+ 1 evaluated in an environment which associates the value 5 to variableX, Written {X→ 5}, gives 6 as result.

In a program, the environment also needs a reasonable representation. You can use data structures such as hash tables, or directly use functions )! In fact, the environment is a function that assigns a specific value to a variable. Environment mentioned above :{X→ 5}, which can be written as follows in scala:

We therefore have to your nd a way to represent environments. We cocould of course use some associative data-structure like a hash table, but we can also directly use functions! An environment is really nothing more than a function which associates a value to a (variable) name. The Environment {X→ 5} Given abve can simply be written as follows in scala:

{Case"X" => 5}

The above Code defines a function. If a string "X" is input to the function as the parameter, the function returns an integer of 5. Otherwise, an exception is thrown.

This notation de calls nes a function which, when given the string "X" as argument, returns the integer 5, and fails with an exception otherwise.

Before writing an expression evaluate function, we must name the type of the environment. Although string => int can be used in all programs, after naming the environment, you can simplify the code and make future modifications more convenient (the Environment name is described here, A simple understanding is a macro, or a custom type ). In Scala, use the following code to complete the name:

Before writing the evaluation function, let us give a name to the type of the environments. we cocould of course always use the type string => int for environments, but it Simpli need es the program if we introduce a name for this type, and makes future changes easier. this is accomplished in Scala with the following notation:

TypeEnvironment = string => int

After that, the type name environment can be used as an alias for the function "convert from string to int.

From then on, the type environment can be used as an alias of the type of functions from string to int.

Now, let's write the evaluate function. The implementation of the evaluate function is intuitive: the sum of the two expressions is equal to the sum of the two expressions, respectively, and then the sum. The value of the variable is directly obtained from the environment; the constant value is equal to the constant itself. It is not difficult to describe this concept in scala:

We can now give the de provided nition of the evaluation function. conceptually, it is very simple: the value of a sum of two expressions is simply the sum of the value of these expressions; the value of a variable is obtained directly from the environment; and the value of a constant is the constant itself. expressing this in Scala is not more DIF extends cult:

Def eval (T:Tree,Env:Environment):InT = TMatch{

Case sum (L,R)=>Eval (L,Env) +Eval (R,Env)

CaseVaR (n) => ENV (N)

CaseConst (v) => V

}

The working principle of the evaluate function is to perform pattern matching on the nodes on the tree T. The following describes the matching process in detail (actually recursion ):

This evaluation function works by discovery MingPattern MatchingOn the tree T. intuitively, the meaning of the above de should nition shoshould be clear:

1. the evaluate function first checks whether tree T is a sum. If yes, the left and right subtree of T are bound to two new variables, L and R, respectively, then, calculate the expression on the right of the arrow (in fact, calculate the left and right subtree values and add them separately. This is a recursion ). The expression on the right of the arrow can use the variable bound to the left of the arrow, that is, L and R. It reads rst checks if the tree T is a sum, and if it is, it binds the left sub-tree to a new variable called L and the right sub-tree to a variable called R, and then proceeds with the evaluation of the expression following the arrow; this expression can (and does) Make use of the variables bound by the pattern appearing on the left of the arrow, I. e. L and R,

2. if the first check does not meet the requirements, that is, if the tree T is not sum, check whether T is a variable VAR. If yes, the name contained in VaR is bound to Variable N, then, execute the logic on the right of the arrow. If the specified rst check does not succeed, that is if the tree is not a sum, it goes on and checks if T is a var; if it is, it binds the name contained in the VaR node to a variable N and proceeds with the right-hand expression,

3. If the second check does not meet the requirements, it means that the tree T is neither sum nor var, and then checks whether T is a constant Const. If yes, the value of the constant is assigned to the variable V, and then the logic on the right of the arrow is executed. If the second check also fails, that is if T is neither a sum nor a var, it checks if it is a const, and if it is, it binds the value contained in the const node to a variable V and proceeds with the right-hand side,

4. At last, if none of the above checks are met, the program will throw an exception, indicating that an error occurs during expression pattern matching. In this case, only the child classes of more trees are declared, but the corresponding pattern matching conditions are not added. Except nally, if all checks fail, an exception is raised to signal the failure of the pattern matching expression; this cocould happen here only if more sub-classes of tree were declared.

Through the above example, we can see that the pattern matching process is actually comparing a value with a series of patterns. If it can match, then the value) take out useful parts (parts) for naming, and then use these named parts (as parameters) to drive the execution of another piece of code.

We see that the basic idea of pattern matching is to attempt to match a value to a series of patterns, and as soon as a pattern matches, extract and name various parts of the value, to define nally evaluate some code which typically makes use of these named parts.

An experienced (seasoned, sophisticated) Object-oriented programmer may ask: why not define eval as a member method of a class tree? In fact, this is also done, because in Scala, condition classes can define methods like normal classes. However, in addition to the differences in programming style, pattern matching and class method have their own advantages and disadvantages. The policy makers must make trade-offs and choices based on the scalability requirements of the program:

A seasoned object-oriented programmer might wonder why we did not de fi ne eval asMethodOf class tree and its subclasses. we coshould have done it actually, since Scala allows method de into Nitions in case classes just like in normal classes. deciding whether to use pattern matching or methods is therefore a matter of taste, but it also has important implications on extensibility:

· Using the class method to add a new node type is relatively simple, because you only need to add a subclass of the tree. However, it is troublesome to add a new operation to the tree, because it is necessary to modify all subclasses of the tree.

· When using methods, it is easy to add a new kind of node as this can be done just by De warning ning the sub-class of tree for it; on the other hand, adding a new operation to manipulate the tree is tedious, as it requires Modi operations cations to all sub-classes of tree,

· Use pattern matching, the opposite is true: adding a new node type requires modifying all the pattern matching functions acting on the tree, while adding new operations is relatively simple, you only need to add a new function.

· When using pattern matching, the situation is reversed: adding a new kind of node requires the Modi specified cation of all funich which do pattern matching on the tree, to take the new node into account; on the other hand, adding a new operation is easy, by just de should Ning it as an independent function.

To further explore pattern matching, we need to define a new operation on the arithmetic expression: Evaluate the symbolic derivation and derivative ). The operation rules are as follows:

To conform e pattern matching further, let us de sort ne another operation on arithmetic expressions: symbolic derivation. The reader might remember the following rules regarding this operation:

1. The sum is equal to the sum of the given sums. The derivative of a sum is the sum of the derivatives,

2. For VariablesVThere are two cases of derivation: If the variableVReturns 1 if it is just a symbol used for evaluation and export. Otherwise, returns 0. The derivative of some variableVIs one ifVIs the variable relative to which the derivation takes place, and zero otherwise,

3. The constant returns 0. The derivative of a constant is zero.

These rules can almost be directly translated into scala code:

These rules can be translated almost literally into scala code, to obtain the following de into nition:

DefDerive (T: Tree, V: string): Tree = TMatch{

CaseSum (L, R) => sum (derive (L, v), derive (R, V ))

CaseVaR (N)If(V = n) => const (1)

Case_ => Const (0)

}

Based on the definition of the function, two knowledge points related to pattern matching are introduced. First,CaseStatement can containGuard, Which consists of keywordsIfAnd the following expression.GuardThe role isCaseThe matching mode is set to twice. OnlyIfThe matching is successful only when the following expression is true. In this example,GuardEnsure that constant 1 is returned only when the name N of the variable to be evaluated is equal to the value of the currently evaluated variable v. 2. In pattern matching, wildcards (marked as _, underlines) can be used to match any value (equivalent to the default branch of the switch statement in Java ).

This function introduces two new concepts related to pattern matching. First of all,CaseExpression for variables hasGuard, An expression followingIfKeyword. this guard prevents pattern matching from succeeding unless its expression is true. here it is used to make sure that we return the constant 1 only if the name of the variable being derived is the same as the derivation variable v. the second new feature of pattern matching used here isWild-card, Written _, which is a pattern matching any value, without giving it a name.

Pattern matching is very powerful, but limited by the length and positioning of this article, we will not discuss it too much in depth. Next, we will use an example, let's see how to use the two functions defined above. To this end, we compile a main function. In the function, we first create an expression :(X+X) + (7 +Y), And then in the Environment {X→ 5,Y→ 7} evaluate the value of the expression, and finally calculateXAndYDerivative.

We did not distinct e the whole power of pattern matching yet, but we will stop here in order to keep this document short. we still want to see how the two functions abve perform on a real example. for that purpose, let's write a simple main function which performs several operations on the expression (X+X) + (7 +Y): It reads rst computes its value in the Environment {X→ 5,Y→ 7}, then computes its derivative relativeXAnd thenY.

DefMain (ARGs: array [String]) {

ValExp: Tree = sum (VAR ("X"), VAR ("X"), sum (const (7), VAR ("Y ")))

ValEnv: Environment = {Case"X" => 5Case"Y" => 7}

Println ("expression:" + exp)

Println ("EvaluationWithX = 5, y = 7: "+ eval (exp, ENV ))

Println ("derivative relative to X:/N" + derive (exp, "x "))

Println ("derivative relative to Y:/N" + derive (exp, "Y "))

}

Run this program and the output is as follows:

Executing this program, we get the expected output:

Expression: 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 )))

After carefully observing the program output, we found that it would be better to show the result to users after simplification. It is interesting (and tricky) to define a simplification function using pattern matching. You can do exercises on your own.

By examining the output, we see that the result of the derivative shocould be Simpli encoded ed before being presented to the user. de specified ning a basic Simpli specified cation function using pattern matching is an interesting (but surprisingly tricky) problem, left as an exercise for the reader.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.