780 likes | 891 Vues
The essence of object-oriented programming. Programming Fundamentals 25 Feliks Klu ź niak. Consider conventional arithmetic expressions, defined by the following grammar: Expression = Term { ( “+” | “–” ) Term } .
E N D
The essence of object-oriented programming Programming Fundamentals 25 Feliks Kluźniak Essence of OOP
Consider conventional arithmetic expressions, defined by the following grammar: Expression = Term { ( “+” | “–” ) Term } . Term = Factor { ( “*” | “div” | “mod” ) Factor } . Factor = Number | Identifier | “(“ Expression “)” . Number = [ “+” | “–” ] Digit { Digit } . Digit = “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9” . Essence of OOP
Consider conventional arithmetic expressions, defined by the following grammar: Expression = Term { ( “+” | “–” ) Term } . Term = Factor { ( “*” | “div” | “mod” ) Factor } . Factor = Number | Identifier | “(“ Expression “)” . Number = [ “+” | “–” ] Digit { Digit } . Digit = “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9” . The following is an example of an expression: (2 + 1 – 4) * ( – (5 + 2) + 6) Essence of OOP
Consider conventional arithmetic expressions, defined by the following grammar: Expression = Term { ( “+” | “–” ) Term } . Term = Factor { ( “*” | “div” | “mod” ) Factor } . Factor = Number | Identifier | “(“ Expression “)” . Number = [ “+” | “–” ] Digit { Digit } . Digit = “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9” . The following is an example of an expression: (2 + 1 – 4) * ( – (5 + 2) + 6) A program that can process such expressions (e.g., evaluate them, or translate them to machine language …) would have a part called a parser (a.k.a., syntactic analyzer) that would convert each expression to a convenient internal format. Essence of OOP
Consider conventional arithmetic expressions, defined by the following grammar: Expression = Term { ( “+” | “–” ) Term } . Term = Factor { ( “*” | “div” | “mod” ) Factor } . Factor = Number | Identifier | “(“ Expression “)” . Number = [ “+” | “–” ] Digit { Digit } . Digit = “0” | “1” | “2” | “3” | “4” | “5” | “6” | “7” | “8” | “9” . The following is an example of an expression: (2 + 1 – 4) * ( – (5 + 2) + 6) A program that can process such expressions (e.g., evaluate them, or translate them to machine language …) would have a part called a parser (a.k.a., syntactic analyzer) that would convert each expression to a convenient internal format. One such convenient internal format is that of a general tree (not a binary tree) that expresses the semantic (as opposed to syntactic) structure of the expression. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. 2 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. 1 2 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. 3 1 2 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. 3 4 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 3 4 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 5 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 5 2 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 7 5 2 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 – 7 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 – 7 6 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. – 1 – 1 – 7 6 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 This form, often called an Abstract Syntax Tree, abstracts away from the details of the textual representation (in this case: parentheses) and explicitly represents the structure of the expression. It is, for instance, very easy to compute the value of the expression from this form. 1 – 1 – 1 Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 Let us try to write a function that would evaluate such a tree. We must first declare the appropriate types. There are three kinds of nodes: Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 21 + 5 2 Let us try to write a function that would evaluate such a tree. We must first declare the appropriate types. There are three kinds of nodes: Value nodes, which represent integers. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 Let us try to write a function that would evaluate such a tree. We must first declare the appropriate types. There are three kinds of nodes: Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * –+ + 4 – 6 2 1 + 5 2 Let us try to write a function that would evaluate such a tree. We must first declare the appropriate types. There are three kinds of nodes: Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. Essence of OOP
(2 + 1 – 4) * ( – (5 + 2) + 6) * – + + 4 – 6 2 1 + 5 2 Let us try to write a function that would evaluate such a tree. We must first declare the appropriate types. There are three kinds of nodes: Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. The main difference between them is in the number of children: a Value node has no children, a UnOp node has one and a BinOp node has two.
We must first declare the appropriate types. There are three kinds of nodes: Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. The main difference between them is in the number of children: a Value node has no children, a UnOp node has one and a BinOp node has two. Essence of OOP
We must first declare the appropriate types. There are three kinds of nodes: Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. The main difference between them is in the number of children: a Value node has no children, a UnOp node has one and a BinOp node has two. The operators themselves can be encoded as integers, for example: constop_add = 0; % addition (+) constop_sub = 1; % subtraction (–) constop_mult = 2; % multiplication (*) constop_div = 3; % division (div) constop_mod = 4; % modulo (mod) constop_plus = 5; % unary plus (+) constop_minus = 6; % unary minus (–) Essence of OOP
Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. type Value = record% An integer value: val : int% the value end Essence of OOP
Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ?% the operand end ; typeBinOp = record% A binary operator: op : int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end Essence of OOP
Value nodes, which represent integers. UnOp nodes, which represent applications of unary operators. BinOp nodes, which represent applications of binary operators. type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ?% the operand end ; typeBinOp = record% A binary operator: op : int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op : int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op : int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! So, we have a problem! Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! So, we have a problem! This problem is typical of heterogenous data structures, where the elements are similar in some respects, but different in others. Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! So, we have a problem! This problem is typical of heterogenous data structures, where the elements are similar in some respects, but different in others. Consider, e.g., a structure that contains information about families of people. Some of them will be new-born babies, some will be students, some self-employed etc. Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! So, we have a problem! This problem is typical of heterogenous data structures, where the elements are similar in some respects, but different in others. Consider, e.g., a structure that contains information about families of people. Some of them will be new-born babies, some will be students, some self-employed etc. All of them will have some common characteristics (name, date of birth …), Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! So, we have a problem! This problem is typical of heterogenous data structures, where the elements are similar in some respects, but different in others. Consider, e.g., a structure that contains information about families of people. Some of them will be new-born babies, some will be students, some self-employed etc. All of them will have some common characteristics (name, date of birth …), many will also have information that is relevant only to the particular kind of person (GPA for students, place of employment for adults who are not self-employed ….). Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ?% the right operand end What should be the types of the pointers to children? Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! There are three common ways to solve the problem. Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; type BinOpP = BinOp ^ ; typeBinOp = record% An operator or a value: kind : int; % kind of node op :int;% the operator or the value opnd1 : BinOpP ;% the left operand, if any opnd2 : BinOpP% the right operand, if any end The first is to have only one type of record that will contain fields that are not always used. In our case this would be the BinOp record: by convention, when representing a unary operator we would not use the opnd2 field, and when representing a value we would use just the op field for storing the value. Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; type BinOpP = BinOp ^ ; typeBinOp = record% An operator or a value: kind : int; % kind of node op : int;% the operator or the value opnd1 : BinOpP ;% the left operand, if any opnd2 : BinOpP% the right operand, if any end The first is to have only one type of record that will contain fields that are not always used. In our case this would be the BinOp record: by convention, when representing a unary operator we would not use the opnd2 field, and when representing a value we would use just the op field for storing the value. What are the disadvantages of such a scheme? Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; type BinOpP = BinOp ^ ; typeBinOp = record% An operator or a value: kind : int; % kind of node op : int;% the operator or the value opnd1 : BinOpP ;% the left operand, if any opnd2 : BinOpP% the right operand, if any end The first is to have only one type of record that will contain fields that are not always used. In our case this would be the BinOp record: by convention, when representing a unary operator we would not use the opnd2 field, and when representing a value we would use just the op field for storing the value. The disadvantages of such a scheme: wasted space (not serious here, could be serious in other applications) Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; type BinOpP = BinOp ^ ; typeBinOp = record% An operator or a value: kind : int; % kind of node op : int;% the operator or the value opnd1 : BinOpP ;% the left operand, if any opnd2 : BinOpP% the right operand, if any end The first is to have only one type of record that will contain fields that are not always used. In our case this would be the BinOp record: by convention, when representing a unary operator we would not use the opnd2 field, and when representing a value we would use just the op field for storing the value. The disadvantages of such a scheme: wasted space (not serious here, could be serious in other applications); we have to be very careful to keep our program consistent: there is no help from the compiler, we must just describe our convention and try to stick to it. Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ? % the right operand end Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! The second approach is to have a facility for declaring a discriminated union type (C), a.k.a. a record with variants (Pascal). The idea is that one can declare that various groups of fields are alternatives, i.e., can never occur together. There is also a tag field that can be used to indicate which of the alternatives is the right one. Essence of OOP
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ? % the right operand end Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! The second approach is to have a facility for declaring a discriminated union type (C), a.k.a. a record with variants (Pascal). The idea is that one can declare that various groups of fields are alternatives, i.e., can never occur together. There is also a tag field that can be used to indicate which of the alternatives is the right one. For example, in Pascal: type Exp = record case arity : intof 0 : (val : int ); 1 : (op : int, opnd : ExpP ) 2 : (op : int, opnd1, opnd2 : ExpP ) end
type Value = record% An integer value: val : int% the value end ; typeUnOp = record % A unary operator: op : int; % the operator opnd : ? % the operand end ; typeBinOp = record% A binary operator: op :int; % the operator opnd1 : ? ; % the left operand opnd2 : ? % the right operand end Notice that the operand of a unary operator might be a Value, a UnOp, or a BinOp ! type Exp = record case arity : intof 0 : (val : int ); 1 : (op : int, opnd : ExpP ) 2 : (op : int, opnd1, opnd2 : ExpP ) end We can now write, e.g., if p ^. arity = 1 then p := p ^ . opnd …. arity: op/val: opnd/opnd1: opnd2: Essence of OOP