Copyright 1984 by ABComputing July 15, 1984 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º The Ada Tutorial - Introduction to Data Types º º º º by º º º º George Gordon Noel º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Ada supports the following data types: ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ Figure 1 ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ ³ ³ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ scalar composite others (exotic) ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÁÄÄÄÄ¿ ÚÄÄÄÄÄÁÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ real discrete arrays records ³ ³ ³ ³ ³ ³ ³ ³ ÚÄÄÄÄÄÁÄÄÄÄ¿ ³ ³ ³ ³ ³ ³ ³ ³ ³ ³ integer enumeration ³ ³ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ As you can see, Ada has MANY data types. My articles on Ada data types will be concerned primarily with scalars and composite types. This article is concerned with scalar types and types in general. The composite types will appear in later articles, and the "others," which are highly specialized, won't be turning up for quite awhile. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Declaring Data Types ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Variables ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Before working with any data type, a data object belonging to that type must be "declared." As we saw in the previous issue, this can be done in the "declarative part" of a procedure between the keywords "is" and "begin": procedure DEMONSTRATE_DECLARATION is X: integer; Y, Z: integer; ZERO: integer := 0; LOW: integer range 0..10; begin ... This program fragment establishes the existence of five integer data objects. In each case, this is done by writing the identifier of the object(s), a colon, and the word integer. When the Ada compiler sees the declaration of X, it reserves storage in computer memory for an integer value, and then associates the identifier X with that area of storage. The second declaration creates two data objects of the same type by writing their identifiers separated by a comma. The declaration of ZERO illustrates how a newly declared object can be initialized, to the number 0 in this case. X, Y, and Z must be assigned values by an assignment statement or I/O operation prior to their use. The last declaration includes a "range constraint," indicated by the keyword "range," and followed by the lower (0) and upper boundaries (10) of the range. The boundaries of the range are separated by two periods. LOW can be given values only in the specified range, meaning that the assignment statement LOW := 20; is incorrect. Constrained objects like LOW can also be initialized. LOW could have been declared like this: LOW: integer range 0..10 := 1; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Constants ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Our five integer objects are all "variables," meaning that their values can change during the execution of a program. The value of other integer objects cannot be changed: procedure UNCHANGING_INTEGER_OBJECTS is NINE: constant integer := 9; DAYS_IN_WEEK: constant integer := 7; TOES_ON_FOOT: constant := 10; begin NINE := 32: -- gives an error message ... The first two declarations are of integer "constants," which are declared like integer variables except for the inclusion of the keyword "constant" before the type name. Constant objects cannot be altered during the course of a program, hence an error message is generated by the reassignment of NINE. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Named-numbers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The third declaration (TOES_ON_FOOT) is that of a "named-number," not a constant. The name of the data type is not included in the declaration of a named-number; only the keyword "constant" is. Ada can tell that TOES_ON_FOOT is an integer by looking at the initial value (integer numbers have no decimal point, as noted in the previous issue). But what kind of integer? As this is not specified, Ada is free to choose. Standard Ada has three built-in integer types. They are: short_integer -- for numbers of small magnitude integer -- for numbers of medium magnitude long_integer -- for numbers of large magnitude. The difference between these types is based on the number of bits used to represent objects of each type. A typical Ada compiler might use 8 bits to represent objects of the short_integer type and 16 bits for the regular integer type. Consider the next two declarations: SHORTY: short_integer; MEDIUM: integer; If short_integer is represented with 8 bits, and "integer" with 16 bits, then SHORTY can take values in the range -128..127 and MEDIUM can take values in the range -32768..32767. The long_integer types have an even broader range of values. When an integer valued named-number is declared, Ada selects the integer type that has just enough bits to represent the given value. Assuming the ranges given above, Ada would choose the short_integer type to represent TOES_ON_FOOT. The main advantage of this approach is that programs using named-numbers are more portable, that is, easier to recompile on different computer systems, than those using standard constants. Because of architectural variations, the ranges of the three integer types may not be the same on different computers. The declaration TWO_SIXTY: constant short_integer := 260; is acceptable on a computer using 12 bits to represent short_integer objects. But using this declaration on a machine with an 8-bit short_integer type will generate an error message. A solution is to declare TWO_SIXTY as a named-number, like this: TWO_SIXTY: constant := 260; and let Ada determine the correct type for the underlying hardware. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Attributes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The ranges of the individual integer types, on a particular machine, can be determined by using a feature in Ada called an "attribute." The attributes FIRST and LAST are used to find the ranges of the built-in integer types. When applied to a data type, FIRST returns the lowest value of that type and LAST the highest value. To use these attributes, follow the type name by an apostrophe, and then the name of the attribute. The statement put (integer'FIRST); prints the value -32768, assuming a 16-bit integer type. For 8-bit short integers, the statement put (short_integer'LAST); prints 127. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Creating Data Types ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ If the three built-in integer types do not meet your needs, custom data types can be created for any application. To create a data type, write the keyword "type," followed by the name of the type, the keyword "is", and some information about the values that make up the type. For user-defined integer types, this information is given by a range constraint. Here are some examples: type NINETEEN_EIGHTIES is range 1980..1989; type JULY_DAYS is range 1..31; type MEMORY_ADDRESS is range 0..2E16 - 1; Notice that the word "integer" does not appear anywhere. As with named-numbers, Ada recognizes integer types from the integer literals in the range. Also like named-numbers, these user-defined integer types are more portable than the built-in types. After declaring the types, constants and variables belonging to those types can be declared: TODAY: JULY_DAYS := 15; THIS_YEAR: NINETEEN_EIGHTIES := 1984; NEXT_YEAR: NINETEEN_EIGHTIES := THIS_YEAR + 1; ORWELL_YEAR: constant NINETEEN_EIGHTIES := THIS_YEAR; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Strong Typing ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Because of Ada's strict segregation of data types, the three integer types just declared are totally incompatible with each other. That is, if a program contains the previous declarations the following assignment is incorrect TODAY := THIS_YEAR; -- Ooops! Violates type rules and an error message is issued. This is because a variable of one type is being assigned to a variable of another type. This is one of the most common errors in Ada. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Subtypes ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Sometimes, we need two different data types that are logically related. An example of this might involve the MEMORY_ADDRESS type, designed to represent the possible addresses in the memory space of a hypothetical computer. To define a new integer type, ROM, that represents addresses in the upper 8K of a ROM memory, use: type ROM is range 57345..65537; ROM_BASE_ADDRESS: ROM := ROM'FIRST; -- you can use the -- FIRST attribute -- on a new type Suppose a variable called ADDRESS had been declared like this: type MEMORY_ADDRESS is range 0..2E16 - 1; ADDRESS: MEMORY_ADDRESS; ADDRESS can take on any possible address value in the memory space. How can we determine if the value of ADDRESS is in the range of ROM? It may seem natural to code the following: if ADDRESS >= ROM_BASE_ADDRESS then put ("You are in ROM"); else put ("You are in RAM"); end if; Unfortunately, this is incorrect as a value of type MEMORY_ADDRESS (the variable ADDRESS) is being compared with a value of type ROM (ROM_BASE_ADDRESS). This is a violation of the rules of strong typing. For cases like this, when two logically related and compatible data types are needed, Ada offers a useful feature called a "subtype." Subtypes encompass only some of the values of their "base types." An integer subtype is declared, and a value of that type created, below: subtype ONE_TO_TEN is integer range 1..10; LOW_NUMBER: ONE_TO_TEN; The subtype declaration looks like other type declarations we have seen, except for the initial keyword "subtype" and the inclusion of the name of the base type ("integer" in this case). The usual rules regarding ranges are obeyed with subtypes. Assigning the value 11 to LOW_NUMBER would produce an error message. The advantage of subtypes is that they may mingle with objects belonging to the base type. This is illustrated in the following procedure, which solves the memory-address problem shown earlier. with TEXT_IO; use TEXT_IO; procedure ROAM_AROUND_MEMORY is type MEMORY_ADDRESS is range 0..2E16 - 1; HIMEM: MEMORY_ADDRESS := MEMORY_ADDRESS'LAST; -- 65535 subtype ROM is MEMORY_ADDRESS range 57345..HIMEM; ROM_BASE_ADDRESS: ROM := ROM'FIRST; ADDRESS: MEMORY_ADDRESS; begin ADDRESS := ROM_BASE_ADDRESS; if ADDRESS >= ROM_BASE_ADDRESS then put ("You are in ROM"); else put ("You are in RAM"); end if; end ROAM_AROUND_MEMORY; This procedure will run properly, although it will only print the message "You are in ROM." It does show, however, that integer variables and variables belonging to an integer subtype may be assigned (as in the first statement after the keyword "begin"), compared (in the "if" statement, which no longer generates an error message), and otherwise mixed together. This compatibility is not unique to integer types. It works with every data type in Ada. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ General Discussion of Types ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Looking back at Figure 1, notice that integer types belong to the "discrete" family of data types. A discrete type has values that are separated by distinct "gaps." The integer type is discrete, as there are no intermediate values between 0 and 1. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Enumeration Types ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The discrete family also includes the "enumeration" types. The values of an enumeration type must be written out, or enumerated, by the programmer when the type is defined. This is done by writing a list of possible values in the form of standard Ada identifiers, enclosed in parentheses and separated by commas. Some examples follow: type SIZE is (SMALL, MEDIUM, LARGE); type COMPUTER is (IBM_PC, APPLE_II, UNIVAC); type IO_DEVICE is (CONSOLE, DISK, PRINTER, TAPE); These new enumeration types behave like ordinary types. Enumeration variables and constants can also be declared: CURRENT_DEVICE: IO_DEVICE; BEST_COMPUTER: constant COMPUTER := IBM_PC; and their values compared: if CURRENT_DEVICE = PRINTER then put ("Hi, printer!"); end if; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Character Literals ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In addition to identifiers, the values of an enumeration type can be "character literals." These are single characters written between single quote marks - 'A', '5', and '*' are some examples. An enumeration type that uses character literals is: type PUNCTUATION is ('!', '.', '?', '&'); The following declaration and statement will print an exclamation point on the screen: EXCLAMATION_POINT: constant PUNCTUATION := '!'; ... put (EXCLAMATION_POINT); ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Boolean Type ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Ada comes with two important built-in enumeration types. The Boolean type is defined by: type Boolean is (TRUE, FALSE); Boolean values are like the LOGICAL values in FORTRAN - they are used to test whether some condition is true: FIRE_ALARM: Boolean := TRUE; ... if FIRE_ALARM then put ("Call the fire department!"); end if; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ CHARACTER Type ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The other important enumeration type is the CHARACTER type. The 128 values of this data type are, naturally, character literals. They encompass the entire ASCII character set, which includes all of the characters on your keyboard and special-purpose characters. As the definition of the CHARACTER type is too long to reproduce, study it in an Ada textbook. Some examples illustrating the CHARACTER type follow. SQUIGGLE: CHARACTER := '~'; subtype LOWER_CASE_LETTERS is CHARACTER range 'a'..'z'; subtype UPPER_CASE_LETTERS is CHARACTER range 'A'..'Z'; ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The Attributes POS and VAL ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ How can characters that are not present on the keyboard be represented in Ada? For example, how is a CHARACTER variable having the value of "carriage return" represented? The simplest way uses the two attributes VAL and POS. The values in a discrete type are ordered in a specific sequence. Each value is assigned a number depending on its position in that sequence. In an enumeration type, the first value listed is number 0, the second 1, and so on. The POS attribute, when appended to an enumeration type, returns the ordinal number corresponding to a value of that type, given in parentheses after the POS attribute. For example, the expression LOWER_CASE_LETTERS'POS('b') returns 1 and the expression COMPUTER'POS(IBM_PC) returns 0. The VAL attribute, conversely, takes an ordinal number and returns the enumeration value corresponding to that number: COMPUTER'VAL(0) gives IBM_PC, and LOWER_CASE_LETTERS'VAL(3) gives 'd'. We use the VAL attribute to access the carriage-return character, which is the thirteenth character in the ASCII character set (according to Ada's ordering scheme). The following declaration and statement sends a carriage-return character to the screen: CR: constant CHARACTER := CHARACTER'VAL(13); ... put (CR); ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Scalar Data Types ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ This concludes our discussion of discrete data types, both integer and enumeration. Look at Figure 1, and notice that the discrete types are a member of the "scalar" family of data types. Scalar variables and constants contain a single data value, as opposed to the "composite" types - arrays and records - that may contain many values. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Real Numbers ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ The scalar family also includes real numbers - numbers with a fractional part. Real numbers are not discrete since there is a continuum of values between any two real numbers. Real numbers are not covered in depth because: (1) we will be working almost exclusively with integers and, (2) the topic of real numbers in Ada is extremely complex and beyond the scope of these articles. Ada has three built-in real types for representing floating-point numbers: short_float, float, and long_float. Some sample declarations are: TIME, DISTANCE: float; TOLERANCE: float := 0.125; PRESSURE: float range 0.0..100.5 := 5.5; MAXIMUM: constant float := 25.5; PI: constant := 3.14159; The third declaration shows that floating-point variables may be constrained by a range of values, and the last declaration shows a named real-number. Real types can also be user-defined. The keyword "digits" is followed by an "accuracy constraint" which indicates the number of significant digits objects of that type will have. type MY_FLOAT is digits 3; Objects of type MY_FLOAT have three significant digits. User-defined real types can include an optional range constraint: type ZERO_TO_ONE is digits 5 range 0.0..1.0; You may be curious about why I refer to the family of "real" numbers when the built-in data type is called "float." Ada also supports another real data type, fixed-point numbers, which are useful in applications where rounding error must be minimized. Ada has no built-in fixed-point data type; fixed-point numbers must be user-defined. The topic of real numbers in Ada is very involved and much of the information would be of interest only to specialists in numerical analysis. The lessons we learned in this article will be sufficient for our needs. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Conclusion ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ Data types will reappear later when we discuss arrays and records. Also, bits and pieces of information about data types will trickle in throughout this series. You may be getting some idea of the sheer size of Ada. Next time: control structures. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ File Name: ÛÛ ada1.txt ÛÛ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ