You can work with data types in the following ways:
| Summary: | Use the isa operator to
check
whether an expression has a specified data type. |
To determine whether a specified expression has a specified data
type, use the
isa operator. The
isa operator
has the following syntax:
| Syntax: | expr isa type
|
| where: |
| expr | is the expression whose data type
you want to compare with
type. |
| type | is the data type that you want to
compare with the type of
expr. |
|
If you supply a literal value for
expr, this operator
returns
true if the literal value can be stored in a
variable of type
type without changing its
representation; otherwise it returns
false.
For example:
| Example:
Using isa with Literals |
 |
98 isa int... {value 98 isa int}
98 isa int8... {value 98 isa int8}
98 isa int32... {value 98 isa int32}
98 isa int64... {value 98 isa int64}
98 isa float... {value 98 isa float}
98 isa double... {value 98 isa double}
98 isa char... {value 98 isa char}
| |
If
expr is a variable of a primitive type,
this operator returns
true if the variable's data type
is
type or an alias for that type;
otherwise, it returns
false. For example:
| Example:
Using isa with Variables of Primitive Types |
 |
98 (an int) isa int...
{value
let x:int = 98
x isa int
}
98 (an int) isa int32...
{value
let x:int = 98
x isa int32
}
98 (an int8) isa int...
{value
let x:int8 = 98
x isa int
}
| |
If
expr is a variable of a class type, this
operator returns
true if the variable's data type
is either
type or a subclass of
type;
otherwise, it returns
false. For example:
| Example:
Using isa with Variables of Class Types |
 |
{define-class Animal}
{define-class Cow {inherits Animal}}
{let harriet:Cow = {Cow}}
harriet isa Cow is ... {value harriet isa Cow}
harriet isa Animal is ... {value harriet isa Animal}
harriet isa double is ... {value harriet isa double}
| |
Note: When you want to check at run time whether an expression has one of
several possible data types, instead of repeatedly testing it in
isa expressions, it may be more efficient to use a single
type-switch construct, as described in the next section.
| Summary: | - Uses the data type of a value to determine which block
of code to execute.
|
The
type-switch expression uses the data type of a value
to determine which block of code to execute. So, for example,
you could execute different blocks of code depending on whether
the runtime value of an
any variable was an
int, a
double, a
Distance, and so on.
The type-switch expression has the following
syntax:
| Syntax: | {type-switch value
[case identifier-1:type-1 do code-block-1]
[case identifier-2:type-2 do code-block-2]
...
[else code-block-n]
}
|
| where: |
| value | is the value whose data type determines which
block of code to execute. |
| type-1, type-2, ... | are the data types with which you want to
compare value for each case
clause. |
| identifier-1, identifier-2, ... | are variable names for each of the possible
values of value. These variable
names may be useful in the code block
that accompanies the case clause. |
| code-block-1, code-block-2, ... | are the code blocks that you want to execute
for each case clause. |
|
When the Curl® runtime encounters a
type-switch expression,
it compares the data type of
value with each of
type-1,
type-2, and so on. It uses the
isa
operator to perform the comparison. When the runtime
encounters the first comparison that returns
true, it
executes the corresponding code (the code that follows the
accompanying
do expression) and then moves to the end of
the
type-switch expression. If none of the comparisons
return
true and there is an
else clause, the runtime
executes the code that follows the
else clause. If
there is no
else clause, the runtime moves to the
expression immediately following the
type-switch
expression.
If the data type of one of the case clauses matches the
data type of value, a type-switch expression
returns the value of the last expression in the code block that is
executed. Otherwise, a type-switch expression does not
return a value.
For example, the following type-switch expression uses the
data type of the return value of a call to the some-function procedure to determine which code to execute. Each
of the case clauses presents a possible data type for the
return value. The runtime executes the code for the first
case clause that matches the type of the return value of
the procedure call. Note that the identifier in each case
clause contains the value being returned. This might be useful in
the code that accompanies the case clause. Also note that,
in this case, the else clause throws an error.
{type-switch {some-function}
case i:int do
{output i, " is an int"}
case f:float do
{output f, " is a float"}
case d:Distance do
{output d, " is a Distance"}
case str:String do
{output str, " is a String"}
else
{error "unexpected return value"}
}
| Summary: | - Use the type-of expression to determine the
runtime data type of an expression.
|
To determine the data type of a specified expression, use
the
type-of expression. The
type-of
expression has the following syntax:
| Syntax: | {type-of expr}
|
| where: |
| expr | is a value, variable, or
expression. |
|
The
type-of expression returns the runtime data type
of an expression. Note that the runtime data type of an
expression might be different from the compile-time data
type of the expression. For example, if you use the
any data type to declare a variable, it has a
compile-time data type of
any. However, the runtime
data type of the variable depends on the value that is
assigned to the variable. Similarly, if you declare a
variable using a class and then assign an instance of a
subclass to the variable, the runtime data type will be the
data type of the subclass.
The following example shows some sample type-of
expressions for primitive data types:
| Example:
Using type-of with Variables of Primitive Types |
 |
{let i:int = 3}
The data type of i is... {type-of i}
{let c:char = 'a'}
The data type of c is... {type-of c}
| |
The following example shows some sample type-of
expressions for the any data type. Remember that
the type-of expression returns the runtime data
type of the variables.
| Example:
Using type-of with Variables of type any |
 |
{let a:any = 3}
The data type of a is... {type-of a}
{let b:any = 'a'}
The data type of b is... {type-of b}
| |
The following example uses type-of with variables
of class types. Remember that the type-of expression
returns the runtime data type of the expression.
| Example:
Using type-of with Variables of Class Types |
 |
|| Declare a variable "c" to be of type "VBox".
|| Initialize "c" with a "VBox" object.
{let c:VBox = {VBox}}
The data type of c is... {type-of c}
|| Declare a variable "d" to be of type "Box".
|| Initialize "d" with a "VBox" object.
|| Note that "Box" is a superclass of "VBox".
{let d:Box = {VBox}}
The data type of d is... {type-of d}
| |
To get the compile-time version of a variable, as opposed
to its runtime type, use the
compile-time-type-of
operator. This is an advanced feature.
You can also use type-of to query the data type of arbitrary expressions.
Here are some simple examples:
| Example:
Using type-of in Expressions |
 |
The data type of 3 is ... {type-of 3}
The data type of 3 * 7.0 is ... {type-of (3 * 7.0)}
The data type of 'a' is ... {type-of 'a'}
The data type of "xyz" is ... {type-of "xyz"}
The data type of {StringBuf "xyz"} is ... {type-of {StringBuf "xyz"}}
| |
Note: For the special default object value
null,
{type-of null} returns
Null.
| Summary: | - Subtype values may be stored in supertype variables.
- Use the Type.subtype-of? method to determine if a type
is a subtype of another.
|
A data type is a subtype of a second data type if its values can be stored
in variables declared with the second type without a change in
representation. A data type is a supertype of a second data type if the
second data type is a subtype of the first. Specifically:
- Every type is its own subtype.
- Every type is a subtype of the any type.
- All class types are subtypes of their direct and
indirect super classes (thus all classes are subtypes of
Object).
- Every type T is a subtype of its null variant type
#T.
- Numeric types are not subtypes of larger numeric
types, even though their values are compatible, because their
representations are not compatible.
To determine if a data type is a subtype of a second data type, use the
subtype-of? method of the
Type class.
| Example:
Testing for Subtypes Using the subtype-of? Method |
 |
String subtype of String: {(String asa Type).subtype-of? String}
String subtype of #String: {(String asa Type).subtype-of? #String}
#String subtype of String: {(#String asa Type).subtype-of? String}
String subtype of Object: {(String asa Type).subtype-of? Object}
String subtype of any: {(String asa Type).subtype-of? any}
int subtype of int64: {int.subtype-of? int64}
| |
| Summary: | - In some cases, the runtime supports the implicit
conversion of values.
- You can use the asa operator
to explicitly convert values.
- You can convert any value to a class that
supports a matching implicit constructor or
factory.
- You can
usually
convert a value with a primitive
type only to another primitive type.
- You can convert a value with a class
type only to another class type.
|
In some cases, you can convert a value to another data type.
If you assign a value with one data type to a variable with
a different data type, the runtime attempts to place the value in
the variable. For example, if you assign a character to an
integer variable, the runtime assigns the numeric value of the
character to the variable. Such conversions are called
implicit conversions because you do not specify
an expression to convert the value. The runtime performs these
conversions automatically. Another instance of an implicit
conversion occurs when a string is converted to an enumerated
type element with the same name in an assignment. (See the
Variables of Enumerated Types section.)
Implicit conversion typically occurs in assignment statements and
when passing values to functions. If you assign a value with one
data type to a variable with a different data type, the runtime attempts
to perform an implicit conversion. Similarly, if you pass a value
with one data type to an argument expecting a different data type,
the runtime attempts to perform an implicit conversion. However, the runtime
will not perform an implicit conversion if the conversion might
result in a loss of data. For example, if you convert an int to an int8, you might lose data because the value of
the int might be outside of the valid range of values for
an int8. If you attempt to implicitly convert a value to a
data type that might result in a loss of data, the Curl® compiler
throws a syntax error.
You can use the asa operator to perform an
explicit conversion of a value from one data
type to another. The asa operator has the following
syntax:
| Syntax: | expr asa type
|
| where: | - expr is an expression whose
type you want to convert.
- type is the data type to which
you want to convert expr.
|
For example:
| Example:
Using asa |
 |
{value
|| Declare a variable "d" with data type double and
|| initialize the variable with the value 37.7 as
|| a double-precision floating-point number.
let d:double = 37.7
|| Display the value of the variable "d" converted
|| to an int.
d asa int
}
| |
Conversion errors detected at compile time will result in a
SyntaxError, while those detected later at run time will result in a
CastException. When deciding whether to perform an implicit or
explicit conversion, you should consider that it is desirable to perform
type checking at compile time rather than at run time. If you convert
values explicitly, it is more likely that the Curl® compiler
will catch an illegal conversion.
The rules for converting primitive data types and class types are
different. In general, you cannot convert a value with a
primitive data type to a value with a class type, and vice versa.
However, any value can be converted to a class that supports an
implicit constructor or factory that can be invoked with the
value. For details on this feature see the documentation for the
implicit keyword.
| Summary: | - A widening conversion involves
converting to a data type that can hold
more information.
- A narrowing conversion involves
converting to a data type that can hold
less information.
- The runtime supports other conversions, such as
converting to and from a char.
- You can convert between all numeric types,
although quantity types must be compatible;
for example, casting int to double
is allowed, but casting Distance to
Time is not.
|
A widening conversion involves converting a value
to a data type that holds more information. That is, the
conversion does not result in loss of data. An example of
a widening conversion is the conversion of an int8 to
an int16. You can perform widening conversions either
implicitly or explicitly.
A narrowing conversion involves the conversion
of data from a type to a data type that can hold less
information. That is, the conversion might result in loss
of data. An example of a narrowing conversion is the
conversion of an int16 to an int8. It is
possible to lose data if the value of the int16 is
outside the range of possible values for an int8.
You must explicitly convert the value to perform a
narrowing conversion.
You can perform both widening conversions and narrowing
conversions on primitive data types. However, there are
exceptions; you cannot convert to a
bool data
type and you cannot convert to or from a quantity. You may
explicitly convert a
bool to any integer type with
an explicit cast (this ability was added in 6.0),
which converts
true and
false to 1 and 0 respectively
of the specified integer type. There are other types of conversions that
do not fit into the widening and narrowing categories, such as:
- Converting between a numeric data type and a
char.
- Converting between integer and floating-point data
types.
- Converting between signed and unsigned integer data
types.
When converting from a numeric data type to a char, the
data is first automatically converted to an integer. The integer
value must be a legal value for char, or an error will
result. The legal values for char are the following
ranges:
- 0x0000 - 0xD7FF
- 0xE000 - 0x10FFFF
You must take care when converting between integer and
floating-point data types because such conversions can result in
the loss of data. During conversions from floating-point numbers
to integers, the decimal portion of the number is truncated. For
example, if the value 37.7 of type double is
converted to the type int, its value changes to 37.
| Example:
Converting Between Integer and Floating-Point
Data Types |
 |
{value
|| Declare a variable "i" with the data type int.
let i:int
|| Assign the value 37.7 to the variable "i".
|| The value 37.7 is explicitly converted to an
|| integer value.
set i = 37.7 asa int
|| Display the value of "i".
i
}
| |
If you assign a large integer value to a variable with
the floating-point data type, you might also experience a loss
of information because of the way that your computer stores
floating-point numbers. For example:
| Example:
Loss of Information During Conversions |
 |
{value
|| Declare a variable "f" with the data type float.
let f:float
|| Assign the value 2147483525 to the variable "a".
|| The value 2147483525 is implicitly converted to
|| a single-precision floating-point value.
set f = 2147483525
|| Display the value of "f" as an int.
f asa int
}
| |
The only conversions that you cannot perform among primitive data
types are conversions to and from
bool or quantity data
types. That is, you cannot convert a
bool value to another
primitive data type and vice versa. And similarly, you cannot
convert a quantity such as
4m to another primitive data
type, such as an
int, or to an incompatible quantity type,
such as
Time, and vice versa.
You must also take care when converting between signed integer and
unsigned integer data types because such conversions might also
result in the loss of data. During a conversion from a signed
integer to an unsigned integer, a negative number might not
convert as you expected. This is because of the way in which
unsigned integers are internally represented.
The following interactive example shows some conversions
from an int8 to a uint8:
| Example:
Performing a Narrowing Conversion |
 |
{value -128 asa uint8}
{value -64 asa uint8}
{value 0 asa uint8}
{value 64 asa uint8}
{value 127 asa uint8}
| |
Of course, these principles can also be applied in reverse to
conversions from a uint8 to an int8. And similar
principles apply to all conversions between signed and unsigned
data types.
| Summary: | - Upcasting converts an object to the type
of a superclass.
- You can implicitly or explicitly upcast
objects.
- After upcasting, you can access only the
members of the superclass.
- Information is not lost when converting
objects.
- Downcasting converts an object to the type
of a subclass.
- You can only explicitly downcast objects.
|
You can convert an object to the class type of a superclass
or a subclass. However, you cannot convert an object to a
class type that is not in the class inheritance tree for
that object.
Converting an object to the class type of a superclass is
called upcasting. That is, upcasting travels
up the class inheritance tree toward a superclass (the
term up is used because the root of a class
inheritance tree is typically at the top, with the leaves
below.)
You can perform an upcast conversion either implicitly or
explicitly. An example of upcasting is the conversion of a
CommandButton to a
Graphic, where
CommandButton is a subclass of
Graphic.
let foo:Graphic = {CommandButton}
A class inherits all of the class members of its superclass.
It also, typically, has its own class members. When you upcast
an object to the type of a superclass, you can access only the
members of the superclass. That is, you cannot access the
members that are defined only in the original class.
However, it is important to realize that the information in the
members that are defined only in the original class is not lost.
Even though this information is inaccessible while the object has
the class type of the superclass, it is still in memory. To
access the information, convert the object back to the type of the
original class. For example:
Converting an object to the type of a subclass is called downcasting. That is, a downcasting travels down the class
inheritance tree towards subclasses. Typically, you will want to
downcast because upcasting has previously occurred. The Curl language does not
implicitly downcast an object; you must use the asa
operator to explicitly convert the object.
To reinforce these concepts, consider the following example where
TextField is a subclass of
BaseFrame. In the code,
the assignment statement implicitly converts the
TextField
to a
BaseFrame:
| Example:
Implicit Upcasting from TextField to BaseFrame |
 |
|| Declare a variable "b" of type BaseFrame.
{let b:BaseFrame = {BaseFrame}}
|| Declare a variable "t" of type TextField, a subclass of BaseFrame.
{let t:TextField = {TextField value = "hello"}}
|| Assign a TextField value to a BaseFrame variable.
|| The value of t is upcast to the BaseFrame type.
{set b = t}
|| Display the object that "b" now refers to
{value b}
|| Check the runtime and compile-time types of "b"
b's runtime type is ... {type-of b},
but its compile-time type is ... {compile-time-type-of b}
| |
The
TextField class includes a
value accessor.
However, the
BaseFrame class does not have a
value
property. Therefore, if you try to get the
value property
for the object
f in the example above, the
compiler will throw a syntax error:
|| Display the value of the object.
b.value
--> Syntax Error!
To use the value getter, you must explicitly
convert the object to a TextField. For example:
| Example:
Explicit Downcasting from BaseFrame to TextField |
 |
{value
|| BaseFrame is a superclass of TextField
let b:BaseFrame = {BaseFrame}
let t:TextField = {TextField value = "hello"}
|| A TextField value is implicitly upcast to the BaseFrame type.
set b = t
|| Explicit downcasting back to a TextField makes this operation possible.
(b asa TextField).value
}
| |
Copyright © 1998-2007 Sumisho Computer Systems Corp.
All rights reserved.
Curl, the Curl logo, Surge, and the Surge logo are trademarks of Sumisho
Computer Systems Corp. that are registered in the United States. Surge
Lab, the Surge Lab logo, and the Surge Lab Visual Layout Editor (VLE)
logo are trademarks of Sumisho Computer Systems Corp.