1172 GENERICITY VERSUS INHERITANCE SB.I package MATRICES is type MATRIX (lines,columns:POSITIVE)is private; function "+(ml,m2:MATRIX)return MATRIX: function "*(ml,m2:MATRIX)return MATRIX: private type MATRIX (lines,columns:POSITIVE)is array (I..lines,1..columns)of G; end MATRICES; Typical generic derivations are: package INTEGER MATRICES is new MATRICES(INTEGER,0,1); package BOOLEAN MATRICES is new MATR/CES (BOOLEAN,false,true,"or","and"); Again,you may omit actual parameters corresponding to formal generic routines (here "+"and"")for type INTEGER,which has matching operations;but you will need them for BOOLEAN.(It is convenient to declare such parameters last in the formal list; otherwise key word notation is required in derivations that omit the corresponding actuals. It is interesting here to take a look at the body (implementation)of such a package: package body MATR/CES is ..Other declarations .. function ""(ml,m2:G)is result:MATRIX (ml'lines,m2'columns); begin if ml'columns /m2'lines then raise incompatible sizes, end if; for i in ml'RANGE(1)loop for j in m2'RANGE(2)loop result (i,j):=zero; for k in mI'RANGE(2)loop result(i,j)=result(i,)+ml(i,k)米m2(k,) end loop; end loop; end loop; return result end"米": end MATRICES. This extract relies on some specific features of Ada: For a parameterized type such as MATRIX (lines,columns:POSITIVE),a variable declaration must provide actual parameters,e.g.mm:MATRIX(100.75);you may then retrieve their values using apostrophe notation,as in mim'lines which in this case has value 100
1172 GENERICITY VERSUS INHERITANCE §B.1 package MATRICES is type MATRIX (lines, columns: POSITIVE) is private; function "+" (m1, m2: MATRIX) return MATRIX; function "✳" (m1, m2: MATRIX) return MATRIX; private type MATRIX (lines, columns: POSITIVE) is array (1 .. lines, 1 .. columns) of G; end MATRICES; Typical generic derivations are: package INTEGER_MATRICES is new MATRICES (INTEGER, 0, 1); package BOOLEAN_MATRICES is new MATRICES (BOOLEAN, false, true, "or", "and"); Again, you may omit actual parameters corresponding to formal generic routines (here "+" and "✳") for type INTEGER, which has matching operations; but you will need them for BOOLEAN. (It is convenient to declare such parameters last in the formal list; otherwise keyword notation is required in derivations that omit the corresponding actuals.) It is interesting here to take a look at the body (implementation) of such a package: package body MATRICES is … Other declarations … function "✳" (m1, m2: G) is result: MATRIX (m1'lines, m2'columns); begin if m1' columns /= m2'lines then raise incompatible_sizes; end if; for i in m1'RANGE(1) loop for j in m2'RANGE(2) loop result (i, j) := zero; for k in m1'RANGE(2) loop result (i, j) := result (i, j) + m1 (i, k) ✳ m2 (k, j) end loop; end loop; end loop; return result end "✳"; end MATRICES; This extract relies on some specific features of Ada: • For a parameterized type such as MATRIX (lines, columns: POSITIVE), a variable declaration must provide actual parameters, e.g. mm: MATRIX (100, 75); you may then retrieve their values using apostrophe notation, as in mm'lines which in this case has value 100
$B.2 INHERITANCE 1173 If a is an array,a'RANGE(i)denotes the range of values in its i-th dimension;for example mI'RANGE(1)above is the same as 1..ml'lines. If requested to multiply two dimension-wise incompatible matrices,the extract raises an exception,corresponding to the violation of an implicit precondition. The minimum and matrix examples are representative of Ada techniques for constrained genericity.They also show a serious limitation of these techniques:only syntactic constraints can be expressed.All that a programmer may require is the presence of certain routines("<=","+",""in the examples)with given types;but the declarations are meaningless unless the routines also satisfy some semantic constraints.Function minimn only makes sense if"<="is a total order relation on G;and to produce a generic derivation of MATR/CES for a type G,you should make sure that operations"+"and""have not just the right signature,GxG-G,but also the appropriate properties:associativity, distributivity,zero a zero element for "+and unity for "etc.We may use the mathematical term ring for a structure equipped with operations enjoying these properties. B.2 INHERITANCE So much for pure genericity.The other term ofthe comparison is inheritance.To contrast it with genericity,consider the example of a general-purpose module library for files.First here is the outline of an implementation of"special files"in the Unix sense,that is to say, files associated with devices: This extract and the class DEVICE feature next few are in the 0-0 notation of the open (file descriptor:INTEGER)is do...end rest of this book. close is do ..end opened:BOOLEAN end--class DEVICE An example use of this class is: dl:DEVICE;fl:INTEGER;... !dl.make;dl.open (f1). if dl.opened then... Consider next the notion of a tape device.For the purposes of this discussion,a tape unit has all the properties of devices,as represented by the three features of class DEVICE, plus the ability to rewind its tape.Rather than building a class from scratch,we may use inheritance to declare class TAPE as an extension-cum-modification of DEV/CE.The new class extends DEVICE by adding a new procedure rewind,describing a mechanism applicable to tapes but not necessarily to other devices;and it modifies some of DEVICE's properties by providing a new version of open,describing the specifics of opening a device that happens to be a tape drive. Objects of type TAPE automatically possess all the features of DEVICE objects,plus their own (here rewind).Class DEVICE could have more heirs,for example D/SK with its own specific features such as direct access read
§B.2 INHERITANCE 1173 • If a is an array, a' RANGE(i) denotes the range of values in its i-th dimension; for example m1'RANGE(1) above is the same as 1 .. m1'lines. • If requested to multiply two dimension-wise incompatible matrices, the extract raises an exception, corresponding to the violation of an implicit precondition. The minimum and matrix examples are representative of Ada techniques for constrained genericity. They also show a serious limitation of these techniques: only syntactic constraints can be expressed. All that a programmer may require is the presence of certain routines ("<=", "+", "✳" in the examples) with given types; but the declarations are meaningless unless the routines also satisfy some semantic constraints. Function minimum only makes sense if "<=" is a total order relation on G; and to produce a generic derivation of MATRICES for a type G, you should make sure that operations "+" and "✳" have not just the right signature, G × G → G, but also the appropriate properties: associativity, distributivity, zero a zero element for "+" and unity for "✳" etc. We may use the mathematical term ring for a structure equipped with operations enjoying these properties. B.2 INHERITANCE So much for pure genericity. The other term of the comparison is inheritance. To contrast it with genericity, consider the example of a general-purpose module library for files. First here is the outline of an implementation of “special files” in the Unix sense, that is to say, files associated with devices: class DEVICE feature open (file_descriptor: INTEGER) is do … end close is do … end opened: BOOLEAN end -- class DEVICE An example use of this class is: d1: DEVICE; f1: INTEGER; … !! d1 ● make; d1 ● open (f1); if d1 ● opened then … Consider next the notion of a tape device. For the purposes of this discussion, a tape unit has all the properties of devices, as represented by the three features of class DEVICE, plus the ability to rewind its tape. Rather than building a class from scratch, we may use inheritance to declare class TAPE as an extension-cum-modification of DEVICE. The new class extends DEVICE by adding a new procedure rewind, describing a mechanism applicable to tapes but not necessarily to other devices; and it modifies some of DEVICE’s properties by providing a new version of open, describing the specifics of opening a device that happens to be a tape drive. Objects of type TAPE automatically possess all the features of DEVICE objects, plus their own (here rewind). Class DEVICE could have more heirs, for example DISK with its own specific features such as direct access read. This extract and the next few are in the O-O notation of the rest of this book
1174 GENERICITY VERSUS INHERITANCE $B.2 Objects of type TAPE will possess all the features of type DEVICE,possibly adapted (in the case of open),and complemented by the new feature rewind. With inheritance comes polymorphism,permitting assignments of the formx=y,This is approximate but only if the type ofx is an ancestor of the type ofy.The next associated property is terminology:“isan dynamic binding:if x is a device,the call x.open (f7)will be executed differently ancestor of"stands for“conforms to” depending on the assignments performed onx before the call:afterx=y,wherey is a tape, Precise rules appear the call will execute the tape version. in earlier chapters. We have seen the remarkable benefits of these inheritance techniques for reusability and extendibility.A key aspect was the Open-Closed principle:a software element such as DEIICE is both usable as it stands (it may be compiled as part of an executable system) and still amenable to extensions (if used as an ancestor of new classes). Next come deferred features and classes.Here we note that Unix devices are a special kind of file;so you may make DEVICE an heir to class FILE,whose other heirs might include TEXT FILE (itself with heirs NORMAL and DIRECTORY)and BINARY FILE. The figure shows the inheritance graph,a tree in this case. open A simple FILE close inheritance hierarchy,with deferred and effective classes DEVICE TEXT FILE BINARY FILE Inherits from DEVICE BINARY FILE *Deferred Although it is possible to open or close any file,how these operations are performed depends on whether the file is a device,a directory etc.So FILE is a deferred class with deferred routines open or close,making descendants responsible for implementing them: deferred class FILE feature open (file_descriptor:INTEGER)is deferred end close is deferred end. end--class FILE Effective descendants of F/LE will provide effective implementations of open and close
1174 GENERICITY VERSUS INHERITANCE §B.2 Objects of type TAPE will possess all the features of type DEVICE, possibly adapted (in the case of open), and complemented by the new feature rewind. With inheritance comes polymorphism, permitting assignments of the form x := y, but only if the type of x is an ancestor of the type of y. The next associated property is dynamic binding: if x is a device, the call x ● open (f1) will be executed differently depending on the assignments performed on x before the call: after x := y, where y is a tape, the call will execute the tape version. We have seen the remarkable benefits of these inheritance techniques for reusability and extendibility. A key aspect was the Open-Closed principle: a software element such as DEVICE is both usable as it stands (it may be compiled as part of an executable system) and still amenable to extensions (if used as an ancestor of new classes). Next come deferred features and classes. Here we note that Unix devices are a special kind of file; so you may make DEVICE an heir to class FILE, whose other heirs might include TEXT_FILE (itself with heirs NORMAL and DIRECTORY) and BINARY_FILE. The figure shows the inheritance graph, a tree in this case. Although it is possible to open or close any file, how these operations are performed depends on whether the file is a device, a directory etc. So FILE is a deferred class with deferred routines open or close, making descendants responsible for implementing them: deferred class FILE feature open (file_descriptor: INTEGER) is deferred end close is deferred end; end -- class FILE Effective descendants of FILE will provide effective implementations of open and close. This is approximate terminology; “is an ancestor of ” stands for “conforms to”. Precise rules appear in earlier chapters. A simple inheritance hierarchy, with deferred and effective classes ∗ FILE DEVICE Inherits from TEXT_FILE BINARY_ FILE ∗ Deferred DEVICE BINARY_ FILE open* close*