If you used other scripting languages, you may find SparForte's type system confusing. In most languages, a type is a primitive representation of data, like an integer or float. Any new types must be built up from object classes.
The type system is used for much more than describing the physical representation of data. It can be used to construct abstractions that represent values from the real world and describe how they relate to each other. In this way, you work at a "higher level", working with concepts and not thinking as much about the physical characteristics of a date type. Using the type system correctly is important for effectively writing quality software.
Every Value Must Have a Type
As you saw in the earlier tutorials, every value has a data type. Every variable or subprogram parameter must be declared with its type. This means that SparForte is a language with static typing (although it has some dynamic typing features which will be described below). At the command prompt, you can assign values to new variable names and SparForte will automatically declare variables and give them the same data type as the expression. The type can be changed with the typeset command, or even deleted with the unset command. These features only work on the command prompt.
In a script, you will have to declare all variables yourself and give them appropriate data types. You can't change the type of a variable nor can you destroy a variable. When a script runs, SparForte performs type checking before any commands execute, catching type errors early.
SparForte is also a strongly-type language, which means that there are restrictions for how different types of data can be mixed. Most strong typing languages will not allow strings to be mixed with numbers in an expression (they are treated as incompatible). However, SparForte uses more strong checking than this, and even structurally equivalent data values may be designated as incompatible in some circumstances.
Creating New Types
New data types can be created using two different statements. The type statement creates a new type derived from an existing type. The subtype statement creates a renaming of an existing type. Using type and subtype, you can create a hierarchy or tree of types without using object classes.
With a new type (created with the type statement), the new type and the parent type are structurally equivalent. You can create any number of new types derived from the parent type but each new type is incompatible with its sibling or its parent types. Although they are all structurally the same, the are logically incompatible.
For example, you can create two new float-based types to represent weight and distance.
type distance_km is new float;
Both distance_km and weight_kg are both types of floats and have the same properties as floats. Any function parameter that expects a float will also accept these new types. But they are incompatible with each other: a distance_km value cannot be assigned to a weight_kg, and a weight_kg function parameter will not accept a distance_km value. Nor can you use either of these new types with float function parameter.
They can be used in situations such as:
The type system is static, which means the types are usually checked before the script is run. If there are type incompatibilities, they will be discovered before anything executes.
The subtype statement works much the same way as the type statement except that a subtype acts like a renaming of the existing type. The new type and the parent type are structurally equivalent. You can create any number of new types derived from the parent type and each new type is compatible with its sibling type. They can all be used interchangeably.
For example, you can create a shorthand name for a type with a long name for an integer-based type like this.
type customer_id is new integer;
cid is an integer type that is a renaming of customer_id and can be used any place that customer_id is used. A customer_id value can be assigned cid variable, or the other way around.
Any two structurally equivalent types can be explicitly treated the same by using a type name as a function. This is called type casting. Use type casting to override type incompatibilities.
For example, suppose you have two types, one for HTML encoded strings and one for unencoded strings.
type html_encoded_string is new string;
If you know that the title variable contains data that doesn't need to be HTML encoded, you can add it to the web_page by converting it to an html_encoded_string value using type casting.
web_page := web_page & html_encoded_string( title );
In SparForte, the positive and natural types are treated specially. These are subranges of the integer type. Positive values are always compatible with natural values, and natural values are always compatible with integer values. But natural values cannot be assigned to a positive, for example, because the range of possible values for a natural is larger than that of the positive type.
When to Use Types and Subtypes
There are different opinions for how to use the type system.
Some developers believe that the primitive, predefined types should only be used to create new types and, in your program, your variables and parameters should only use types that you have created.
Some developers are concerned that too many types can be cumbersome, requiring extra type casting, converting one type to another. For example, if you have a lot of different integer types and you want to use them in an expression, you may have to typecast them all to integer and then typecast the result back to the final type, which makes the expression hard to read.
It is important that you use new types where they make the most sense. If a customer id and a supplier id will never be combined in an expression, then it makes good sense to define them as incompatible types.
There are many predefined types. More types are declared in the built-in packages. These are described in detail in the Reference section on types. The following type tree shows most of the predefined types and how they are related to one another.
As mentioned in the other tutorials, SparForte also has universal types. These are the most primitive types and are the base types of most of the other predefined types. A universal type is special in that it is automatically compatible with any type of the same structure. These are intended for short, simple scripts or working on the command line when you don't want to take advantage of SparForte's typing system.
There are three universal types.
Because these types are root types, if you create a new type using type or subtype, the new type will not have the special properties of the universal type. This shouldn't be much of an obstacle, since you use the universal types to avoid the type system.
These types are similar to weak typing: an universal_typeless variable is always contains a universal_typeless value, but the value is interpreted as a string or integer depending on the expression or context, implicitly type casting the value. This is different from dynamic typing, where the type of a variable is unknown until the program runs.
There is a trade-off when using universal types. They can be more convenient when creating a quick prototype or a short script, but you also lose the safety, readability and easy scaling advantages of explicit types and compile-time error checking. Like any data type, be cautious and chose the approprite type for your task.
Types, Subtypes and Aggregate Types
Another use for the type system is to construct aggregate types such as arrays or records.
Once you define a new aggregate type, you can create derived types using the type and subtype statements just like any other type.
|Back To Top|