4D v13.4

Typing Guide

Home

 
4D v13.4
Typing Guide

Typing Guide  


 

 

This section describes the main causes of typing conflicts on variables, as well as ways to avoid them.

Simple data type conflicts can be summarized as follows:

  • conflict between two uses,
  • conflict between use and a compiler directive,
  • conflict resulting from implicit retyping,
  • conflict between two compiler directives.

The simplest data type conflict is one that stems from a single variable name designating two different objects. Suppose that, in an application, you write:

 Variable:=5

and that elsewhere, in the same application, you write:

 Variable:=True

This generates a data type conflict. The problem can be solved by renaming one of the variables.

Suppose that, in an application, you write:

 Variable:=5

and that elsewhere, in the same application, you write:

 C_BOOLEAN(Variable)

Since the compiler scans the directives first, it will type Variable as Boolean, but when it finds the statement:

 Variable:=5

it detects a data type conflict. You can solve the problem by renaming your variable or modifying the compiler directive.

Using variables of different data types in one expression creates inconsistencies. The compiler points out incompatibilities. Here is a simple example:

 vBool:=True `The compiler infers that vBoolean is data type Boolean
 C_INTEGER(<>vInteger`Declaration of an Integer by a compiler directive
 <>vInteger:=3 `Command compatible with the compiler directive
 Var:=<>vInteger+vBool `Operation containing variables with incompatible data types

Some functions return variables of a very precise data type. Assigning the result of one of such variables to a variable already typed differently will cause a data type conflict if you are not careful.

For example, in an interpreted application, you can write:

 IdentNo:=Request("Identification Number") `IdentNo is data type Text
 If(Ok=1)
    IdentNo:=Num(IdentNo) `IdentNo is data type Real
    QUERY([Contacts]Id=IdentNo)
 End if

In this example, you create a type conflict in the third line. The solution consists in controlling the behavior of the variable. In some cases, you must create an intermediate variable that uses a different name. In other cases, such as this, your method can be structured differently:

 IdentNo:=Num(Request("Identification Number")) `IdentNo is data type Real
 If(Ok=1)
    QUERY([Contacts]Id=IdentNo)
 End if

Declaring the same variable through two conflicting compiler directives constitutes a retyping. If, in the same database, you write:

 C_BOOLEAN(Variable)
 C_TEXT(Variable)

the compiler detects the conflict and reports an error in the error file. Typically, you can solve the problem by renaming one of the variables.

Keep in mind that a data type conflict can arise concerning the use of C_STRING if you modify the maximum string length. Thus, if you write:

 C_STRING(5;MyString)
 MyString:="Hello"
 C_STRING(7;MyString)
 MyString:="Flowers"

the compiler identifies a conflict because it must provide an adequately-sized location when declaring String variables.

The solution is to use a compiler directive that gives the maximum length, since, by default, the compiler will accept a shorter length. You can write:

 C_STRING(7;String)
 String:="Flowers"
 String:="Hello"

Note: If you have written C_STRING(7;String) twice, i.e.:

 C_STRING(7;String)
 String:="Flowers"
 C_STRING(7;String)
 String:="Hello"

the compiler will nevertheless accept the directives; the second directive is simply redundant.

Data type conflicts involving local variables are identical to those in process or interprocess variables. The only difference is that there must be consistency only within a specified method.

For process and interprocess variables, conflicts occur at the general leve of teh database. For local variables, conflicts occur at the level of the method. For example, you cannot write in the same method:

 $Temp:="Flowers"

and then

 $Temp:=5

However, you can write:

 $Temp:="Flowers"

in method M1, and:

 $Temp:=5

in method M2, because the scope of local variables is the method itself and not the entire database.

Conflicts concerning an array are never size-related. As in uncompiled databases, arrays are managed dynamically. The size of an array can vary throughout methods, and you do not have to declare a maximum size for an array.

Therefore, you can size an array to null, add or remove elements, or delete the contents.

You should follow these guidelines when writing a database intended for compilation:

  • Do not change data types of array elements,
  • Do not change the number of dimensions of an array,
  • For a String array, do not change character-string length.

If you declare an array as an Integer array, it must remain an integer array throughout the database. It can never contain, for example, Boolean type elements.

If you write:

 ARRAY INTEGER(MyArray;5)
 ARRAY BOOLEAN(MyArray;5)

the compiler cannot type MyArray.

Just rename one of the arrays.

In an uncompiled database, you can change the number of dimensions in an array. When the compiler sets up the symbol table, one-dimensional arrays and two-dimensional arrays are managed differently.

Consequently, you cannot redeclare a one-dimensional array as two-dimensional, or vice versa.

Therefore, in the same database, you cannot have:

 ARRAY INTEGER(MyArray1;10)
 ARRAY INTEGER(MyArray1;10;10)

However, you can write the following statements in the same application:

 ARRAY INTEGER(MyArray1;10)
 ARRAY INTEGER(MyArray2;10;10)

The number of dimensions in an array cannot be changed in a database. However, you can change the size of an array. You can resize one array of a two-dimensional array and write:

 ARRAY BOOLEAN(MyArray;5)
 ARRAY BOOLEAN(MyArray;10)

Note: A two-dimensional array is, in fact, a set of several one-dimensional arrays. For more information, refer to the Two-dimensional Arrays section.

String arrays follow the same rules as fixed strings, for the same reasons.
If you write:

 ARRAY STRING(5;MyArray;10)
 ARRAY STRING(10;MyArray;10)

the compiler detects a length conflict. The solution is simple: declare the maximum string length. The compiler automatically handles shorter length strings.

When using commands such as COPY ARRAY, LIST TO ARRAY, ARRAY TO LIST, SELECTION TO ARRAY, SELECTION RANGE TO ARRAY, ARRAY TO SELECTION, or DISTINCT VALUES, you may change, voluntarily or not, the data types of elements, the number of dimensions, or, in a String array, the string length. You will thus find yourself in one of the three situations previously mentioned.

The compiler generates an error message; the required correction is usually quite obvious. Examples of implicit array retyping are provided in the Syntax Details section.

If you want to compile a database that uses local arrays (arrays only visible by the methods that created them), you must explicitly declare them in 4D before using them.

Explicitly declaring an array means using a command of the type ARRAY REAL, ARRAY INTEGER, etc.

For example, if a method creates a local integer array of 10 elements, you should have the following line in your method:

 ARRAY INTEGER($MyArray;10)

Variables created in a form (e.g., buttons, drop-down list boxes, and so forth) are always process or interprocess variables.

In an interpreted database, the data type of such variables is not important. However, in compiled applications, it may have to be taken into consideration. The rules are, nevertheless, quite clear:

  • You can type form variables using compiler directives, or
  • The compiler assigns it a default type that can be set in the compilation Preferences (see the Design Reference manual).

The following form variables are typed as Real by default:

Check box
3D check box
Button
Highlight button
Invisible button
3D button
Picture button
Button grid
Radio button
3D radio button
Radio picture
Picture menu
Hierarchical pop-up menu
Hierarchical list
Ruler
Dial
Thermometer

Note: The Ruler, Dial and Thermometer form variables are always typed as Reals, even if you choose Long integer as the Default Button Type in the Preferences.

For one of these variables, the only data type conflict that could arise would be if the name of a variable were identical to that of another one located elsewhere in the database. In this case, rename the second variable.

A graph area is automatically data type Graph (Longint). This variable never creates a data type conflict. For a Graph type variable, the only possible data type conflict that could arise would be if the name of a variable were identical to that of another one located elsewhere in the database. In this case, rename the second variable.

A plug-in area is always a Longint. There can never be a data type conflict.
For a plug-in area, the only possible data type conflict that could arise would be if the name of a variable were identical to that of another one located elsewhere in the database. In this case, rename the second variable.

These variables are of the following types:

Non-enterable variable
Enterable variable
Drop-down list
Menu/drop-down list
Scrollable area
Combo box
Pop-up Menu
Tab control

These variables are divided into two categories:
- simple variables (enterable and non-enterable variables)
- display variables (drop-down lists, menus/drop-down lists, scrollable areas, pop-up menus, combo boxes and tab controls).

  • Simple variables
    Their default data type is Text. When used in methods or object methods, they are assigned the data type selected by you. There is no danger of conflict other than one resulting from assigning the same name to another variable.
  • Display variables
    Some variables are used to display arrays in forms. If default values have been entered in the Form editor, you must explicitly declare the corresponding variables using the Array Declaration commands (ARRAY STRING, ARRAY TEXT...).

When you use pointers in your database, you take advantage of a powerful and versatile 4D tool. The compiler preserves all the benefits of pointers.

A pointer can point to variables of different data types. Do not create a conflict by assigning different data types to a variable. Be careful not to change the data type of a variable to which a pointer refers.

Here is an example of this problem:

 Variable:=5.3
 Pointer:=->Variable
 Pointer->:=6.4
 Pointer->:=False

In this case, your dereferenced pointer is a Real variable. By assigning it a Boolean value, you create a data type conflict.

If you need to use pointers for different purposes in the same method, make sure that your pointers are defined:

 Variable:=5.3
 Pointer:=->Variable
 Pointer->:=6.4
 Bool:=True
 Pointer:=->Bool
 Pointer->:=False

A pointer is always defined in relation to the object to which it refers. That is why the compiler cannot detect data type conflicts created by pointers. In case of a conflict, you will get no error message while you are in the typing phase or in the compilation phase.

This does not mean that the compiler has no way to detect conflicts involving pointers. The compiler can verify your use of pointers when you check the Range Checking option in the compilation Preferences (see the Design Reference manual).

During compilation, the compiler analyzes the definitions of the plug-in commands used in the database, i.e. the number and type of parameters of these commands. There is no danger of confusion at the typing level if your calls are consistent with the declaration of the method.

Make sure that your plug-ins are installed in the PlugIns folder, in one of the locations authorized by 4D: next to the database structure file or next to the executable application (Windows) / in the software package (Mac OS). For compatibility reasons, it is still possible to use the Win4DX or Mac4DX folder next to the structure file. For more information, refer to the Installation Guide of 4D.

The compiler does not duplicate these files, but analyzes them to determine the proper declaration of their routines.
If your plug-ins are located elsewhere, the compiler will ask you to locate them during typing, via an Open file dialog box.

Certain plug-ins, for example 4D Write, implement commands that implicitly call 4D commands.

Take the example of 4D Write. The syntax for the WR ON EVENT command is:
WR ON EVENT(area;event;eventMethod)

The last parameter is the name of the method that you have created in 4D. This method is called by 4D Write each time the event is received. It automatically receives the following parameters:

ParametersTypeDescription
$0LongintFunction return
$1Longint4D Write area
$2LongintShift key
$3LongintAlt key (Windows); Option key (Mac OS)
$4LongintCtrl key (Windows), Command key (Mac OS)
$5LongintType of event
$6 LongintValue depends on the Event parameter

For the compiler to take these parameters into account, you must make sure that they have been typed, either by a compiler directive, or by their usage in the method. If they have been used procedurally, the usage has to be explicit enough to be able to deduce the type clearly.

4D can be used to create and work with components. A 4D component is a set of 4D objects representing one or more functionalities and grouped in a structure file (called the matrix database), that can be installed in different databases (called host databases).

A host database running in interpreted mode can use either interpreted or compiled components indifferently. It is possible to install both interpreted and compiled components in the same host database. On the other hand, a host database running in compiled mode cannot use interpreted components. In this case, only compiled components can be used.

An interpreted host database containing interpreted components can be compiled if it does not call any methods of the interpreted component. If this is not the case, a warning dialog box appears when you attempt to compile the application and compilation is not possible.

A naming conflict can occur when a shared project method of the component has the same name as a project method of the host database. In this case, when the code is executed in the context of the host database, the method of the host database is called. This means that it is possible to “mask” the method of the component with a custom method (for example, to obtain a different functionality). When the code is executed in the context of the component, the method of the component is called. This masking will be indicated by a warning in the event of compilation of the host database.

If two components share methods having the same name, an error is generated when the host database is compiled.

For more information about components, please refer to the Design Reference manual. 

The handling of local variables follows all the rules that have already been stated. As with all other variables, their data types cannot be altered while the method executes. In this section, we examine two instances that could lead to data type conflicts:

  • When you actually require retyping. The use of pointers helps avoid data type conflicts.
  • When you need to address parameters by indirection.

A variable cannot be retyped. However, it is possible to use a pointer to refer to variables of different data types.

As an example, consider a function that returns the memory size of a one-dimensional array. In all but two cases, the result is a Real; for Text arrays and Picture arrays, the memory size depends on values that cannot be expressed numerically (see the Arrays and Memory section).

For Text and Picture arrays, the result is returned as a string of characters. This function requires a parameter: a pointer to the array whose memory size we want to know.

There are two methods to carry out this operation:

  • Work with local variables without worrying about their data types; in such case, the method runs only in interpreted mode.
  • Use pointers, and proceed in interpreted or in compiled mode.

MemSize function, only in interpreted mode (example for Macintosh)

 $Size:=Size of array($1->)
 $Type:=Type($1->)
 Case of
    :($Type=Real array)
       $0:=8+($Size*10) ` $0 is a Real
    :($Type=Integer array)
       $0:=8+($Size*2)
    :($Type=LongInt array)
       $0:=8+($Size*4)
    :($Type=Date array)
       $0:=8+($Size*6)
    :($Type=Text array)
       $0:=String(8+($Size*4))+("+Sum of text lengths") ` $0 is a Text
    :($Type=Picture array)
       $0:=String(8+($Size*4))+("+Sum of picture sizes") ` $0 is a Text
    :($Type=Pointer array)
       $0:=8+($Size*16)
    :($Type=Boolean array)
       $0:=8+($Size/8)
 End case

In the above method, the data type of $0 changes according to the value of $1; therefore, it is not compatible with the compiler.

MemSize function in interpreted and compiled modes (example for Macintosh)

Here, the method is written using pointers:

 $Size:=Size of array($1->)
 $Type:=Type($1->)
 VarNum:=0
 Case of
    :($Type=Real array)
       VarNum:=8+($Size*10) ` VarNum is a Real
    :($Type=Integer array)
       VarNum:=8+($Size*2)
    :($Type=LongInt array)
       VarNum:=8+($Size*4)
    :($Type=Date array)
       VarNum:=8+($Size*6)
    :($Type=Text array)
       VarText:=String(8+($Size*4))+("+Sum of text lengths")
    :($Type=Picture array)
       VarText:=String(8+($Size*4))+("+Sum of picture sizes")
    :($Type=Pointer array)
       VarNum:=8+($Size*16)
    :($Type=Boolean array)
       VarNum:=8+($Size/8)
 End case
 If(VarNum#0)
    $0:=->VarNum
 Else
    $0:=->VarText
 End if

Here are the key differences between the two functions:

  • In the first case, the function's result is the expected variable,
  • In the second case, the function's result is a pointer to that variable. You simply dereference your result.

The compiler manages the power and versatility of parameter indirection. In interpreted mode, 4D gives you a free hand with numbers and data types of parameters. You retain this freedom in compiled mode, provided that you do not introduce data type conflicts and that you do not use more parameters than you passed in the calling method.

To prevent possible conflicts, parameters addressed by indirection must all be of the same data type.

This indirection is best managed if you respect the following convention: if only some of the parameters are addressed by indirection, they should be passed after the others.

Within the method, an indirection address is formatted: ${$i}, where $i is a numeric variable. ${$i} is called a generic parameter.

As an example, consider a function that adds values and returns the sum formatted according to a format that is passed as a parameter. Each time this method is called, the number of values to be added may vary. We must pass the values as parameters to the method and the format in the form of a character string. The number of values can vary from call to call.

This function is called in the following manner:

 Result:=MySum("##0.00";125,2;33,5;24)

In this case, the calling method will get the string “182.70”, which is the sum of the numbers, formatted as specified. The function's parameters must be passed in the correct order: first the format and then the values.

Here is the function, named MySum:

 $Sum:=0
 For($i;2;Count parameters)
    $Sum:=$Sum+${$i}
 End for
 $0:=String($Sum;$1)

This function can now be called in various ways:

 Result:=MySum("##0.00";125,2;33,5;24)
 Result:=MySum("000";1;18;4;23;17)

As with other local variables, it is not necessary to declare generic parameters by compiler directive. When required (in cases of ambiguity or for optimization), it is done using the following syntax:

 C_INTEGER(${4})

This command means that all parameters starting from the fourth (included) will be addressed by indirection and will be of the data type Integer. $1, $2 and $3 can be of any data type. However, if you use $2 by indirection, the data type used will be the generic type. Thus, it will be of the data type Integer, even if for you it was, for instance, of the data type Real.

Note: The compiler uses this command in the typing phase. The number in the declaration has to be a constant and not a variable.

Some 4D variables and constants are assigned a data type and an identity by the compiler. Therefore, you cannot create a new variable, method, function or plug-in command using any of these variables or constant names. You can test their values and use them as you do in interpreted mode.

Here is a complete list of 4D System Variables with their data types.

VariableType
OKLongint
DocumentText
FldDelimitLongint
RecDelimitLongint
ErrorLongint
Error methodText
Error lineLongint
MouseDownLongint
KeyCodeLongint
ModifiersLongint
MouseXLongint
MouseYLongint
MouseProcLongint

When you create a calculated column in a report, 4D automatically creates a variable C1 for the first one, C2 for the second one, C3 and so forth. This is done transparently.
If you use these variables in methods, keep in mind that, like other variables, C1, C2, ... Cn cannot be retyped.

A complete list of the predefined constants in 4D can be found using the List of constant themes. 4D constants are also displayed in the Explorer in Design mode.

 
PROPERTIES 

Product: 4D
Theme: Compiler

 
SEE ALSO 

Error messages
Optimization Hints
Syntax Details
System Variables
Using Compiler Directives