Variables

This section describes variables in the Curl® language. In particular, this section describes the following:

Introduction to Variables

Summary:
  • Variables can be declared using let or def.
  • Uninitialized variables are implicitly assigned a default value.
  • If you do not declare the type of a variable, it is implicitly inferred from the initial value when using the def syntax and otherwise is assumed to take the any type.
  • Code with variables that have the any data type may take longer to execute and may use more resources.
A variable is a place to store a value in memory. Each variable has an identifier, which is also called a name or a handle, and a data type. If you define a variable in your program, you can then use the name to access the value later in the program. After you define a variable, you can change its value unless it was declared as a constant, whose value you cannot change.
Variables may be declared using the syntaxes let and def, which are described in more detail below. Variables may also be introduced by other syntaxes such as if-non-null or in the case statements of type-switch.
By declaring a variable, you are telling the runtime to reserve memory and assign an initial value to the variable. When you declare a variable, you specify the name of the variable and the data type. The amount of memory that the runtime allocates to the variable depends on the data type. You can optionally initialize the value of the variable when you declare it. If you do not initialize a variable, the runtime assigns a default value to the variable. Variables defined using the def syntax are required to have an explicit initializer.
If you do not declare the type of a variable, the runtime automatically chooses a type. When the variable is declared using def, the type is inferred to be the same as the type of the value assigned to it. When the variable is declared using let, the type is the any data type. The any data type can, as the name implies, store any type of data.
The inclusion of the any data type adds many features to the Curl language. Because beginning developers need not worry about type specifications, their experience with learning to program in the Curl language is often easier. Also, the migration to the Curl language for people who are used to scripting languages that do not have type specifications is easier. Many people argue that code is more readable without type specifications. However, you should take care because often there is a runtime cost associated with using the any data type. This means that, when your code runs, it may take longer to execute and use more resources if you have variables with the any data type. See The any Type section for more information about the any data type.
Because of the potential for typeless let declarations to result in somewhat slower code, the programmer may disable this behavior by by setting the allow-implicit-any-declarations? compiler directive to false, which can also be accomplished by setting one of the directives fast?, safe?, careful? or stringent? to true. See the Compiler Directives chapter for details.
Once you declare a variable, the runtime ensures that you do not assign a value that does not match the data type of the variable. See the Converting Data Types section for more information about conversion and casting.

Choosing Identifiers

Summary:
  • Begin with a letter, ideographic character, or an underscore character.
  • Use only letters, ideographic characters, digits, underscores, question marks, and hyphens.
  • Identifiers are case-sensitive and width-sensitive.
An identifier is a name for a variable, procedure, class, or anything else that someone can create in Curl language source code. When choosing an identifier, use a combination of characters that does not begin with a digit.
Identifiers can be written using a great many characters from the Unicode® character set. Developers whose native alphabet is not Roman can make use of most characters that are used to write meaningful words in their language. For example, Japanese developers may use kanji, hiragana, katakana, Romaji, and ASCII characters in their variable names. Variables are case-sensitive and width-sensitive.
In particular, follow these rules when choosing identifiers:
Click the following links to see examples of language specific identifiers.
The syntax of the Curl language, unlike most other programming languages, contains many special terms that are not technically reserved words. It is therefore legal, though not advisable, to use these Curl language terms as identifiers. For example, the word field can be used when defining a class to reserve room for a datum of a specified type within each instance of the new type. In the Curl language, you can declare a variable with the identifier field, or even use this word as the name of a field for some class. However, although this is possible, it makes code hard to read and harder to debug. Therefore we recommend that you do not use Curl language commands and modifiers as identifiers.
When creating identifiers, you can choose any valid identifier. You should choose identifiers which help make your code easy to understand for yourself and for other developers who may need to read or maintain it. For consistency, we have adopted the following guidelines for code written and maintained by developers whose native script uses the Roman alphabet. Developers whose native script uses ideographic characters, such as Japanese, may wish to establish similar guidelines or conventions in their code.
Type of IdentifierGuidelineExample
PackageUse all uppercase letters, with a hyphen between each word.PACKAGE-NAME
ClassFor each word, use an uppercase letter for the first character.ClassName
Class MemberUse all lowercase letters, with a hyphen between each word.class-member-name
ProcedureUse all lowercase letters, with a hyphen between each word.procedure-name
Variable or ConstantUse all lowercase letters, with a hyphen between each word.variable-name
Enumerated TypeFor each word, use an uppercase letter for the first character.MyType
Element of an Enumerated TypeUse all lowercase letters, with a hyphen between each word.type-element

Reserved Words

Summary:
  • When choosing an identifier, do not use a reserved word.
When choosing an identifier, do not use a reserved word. The following are reserved words in this release of the Curl language:
...andasaconstruct-supercurl-file-signaturedefdivfalseisalet
In addition, there are certain contexts in which defining a variable with the name of a syntactic key word (other than those listed in the table above) will make it impossible for the runtime to parse your code. Examples of such contexts include:

Declaring Global Variables and Constants

You can declare global variables and constants only in top-level code. (Remember that top-level code is code that is not nested within another code block.) In an applet, you must enclose the let or def statement that declares global variables and constants in curly braces. Otherwise, the runtime treats your declaration as ordinary text and displays it. In a package, you need not enclose the let/def statement in curly braces, but as a matter of style we recommend using them for global variables in order to distinguish them from local variables declared using let/def. The syntax of the let/def statement for declaring a global variable or constant is as follows:
Syntax: {let [access] [modifier-list] name[:type][ = value]}

or

{def [access] [modifier-list] name[:type] = value}
where:
accessis the optional access attribute. Valid attributes are public and package. If you do not specify access, the access attribute defaults to package.

Specify public to declare a public variable or constant. All source code can access a public variable or constant.

Specify package to declare a package-protected variable or constant. Only code in the same package can access the variable or constant.
modifier-listis the optional list of modifiers. The only valid modifiers are constant and deprecated. Specify constant if you want to declare a global constant. If you specify constant, an explicit value must also be provided When using the def syntax, the constant attribute is automatically assumed and may not be explicitly specified. Use deprecated if this global variable or constant is not recommended for use in new code and may be removed in a future version. If you specify more than one modifier, use spaces to separate the modifiers.
nameis the name of the variable or constant. The Curl language naming conventions for variables suggest using lowercase letters and a hyphen to join multiple words (for example, variable-name).
typeis the data type of the variable or constant. Specify any valid data type. type is required if value is not specified. If type is not specified, then any is assumed when using the let syntax and is assumed to be the same as the compile-time type of value under the def syntax.
valueis the value of the variable or constant. value is optional for variables, but required for constants. value is required if type is not specified. If value is not specified, then null or 0 or false is assumed, as appropriate.
For example:
{let int-var:int = 4}
{let dbl-var:double}
{let constant int-const:int = 9}
{let constant char-const:char = 'a'}
{let public char-var:char}
{def pi = 3.14159}
Note: You can supply access and modifier-list in any order. For example, both
let public constant ...
and
let constant public ...
are valid.
If multiple variables or constants have identical modifiers and access attributes, you can use a single let/def statement to declare them. Use the following syntax:
Syntax: {let [modifier] [access] name[:type][ = value],
      name[:type][ = value],
      ...
      name[:type][ = value]
}


or

{def [modifier] [access] name[:type] = value,
      name[:type] = value,
      ...
      name[:type] = value
}
For example:
{let public
    int-var:int = 4,
    dbl-var:double
}
{def public
    int-const = 9,
    char-const = 'a'
}

Declaring Local Variables and Constants

Within a code block, you can declare local variables and constants. The scope of the variables is limited to the code block in which they are declared. For local variables, the use of curly braces to surround the let/def statement is optional, and as a matter of style is discouraged. A local variable defined using let can be declared as constant, although it is usually easier to simply use the def syntax. The syntax of the let/def statement for declaring a local variable is as follows:
Syntax: let [constant] name[:type][ = value]

or

def name[:type] = {metvar value}
where:
nameis the name of the variable. The Curl language naming conventions for variables suggest using lowercase letters and a hyphen to join multiple words (for example, variable-name).
typeis the data type of the variable. Specify any valid data type. type is required if value is not specified. If type is not specified, then any is assumed when using the let syntax and is assumed to be the same as the compile-time type of value under the def syntax.
valueis the initial value of the variable. value is required if type is not specified. If value is not specified, then null or 0 or false is assumed, as appropriate.

If the variable has the constant attribute, you need to supply an initial value.
For example:
{do
    let int-var:int = 4
    let dbl-var:double
    let char-var = 'z'
    def message = "Hello World"
}
Also, you can use one let/def statement to declare more than one local variable. Use the following syntax:
Syntax: let name[:type][ = value],
    name[:type][ = value],
    ...
    name[:type][ = value]


or

def name[:type] = value,
    name[:type] = value,
    ...
    name[:type] = value
For example:
{do
    let int-var:int = 4,
        dbl-var:double,
        char-var = 'z'

    def one = 1, two = 2
}

Declaring Class Variables and Constants

A class variable is like a global variable. However, you also need to use the class name when referring to the variable. Additionally, curly braces are optional around the variable declaration, since it is not in top-level code. When declaring class variables, you can specify the usual access attributes and modifier for class members. For more information, see Class Constants, Variables, and Procedures.

Differences between let and def

The def syntax was introduced in version 6.0 of the Curl API. It allows constants to be defined without having to explicitly specify the type. The principle differences between let and def are that def is implictly constant, def requires an explicit value expression, and that the type is optional even under stringent compiler directives. Instead of assuming the type is any, as would be the case when using let, the def syntax instead infers the type from the value expression used to initialize the constant(s). The inferred type is the compile-time type of the value expression, not its runtime type; that is, it is the type that can be determined when the value expression is compiled, where the actual runtime value may be of a more specific type. For def statements with one variable per initializer, the statement
def name = value
is generally equivalent to
let constant name:{compile-time-type-of value } = value
For example, the code-body
def message = "hi"
def numbers = {IntVec 1,2,3,4}
is the same as
let constant message:String = "hi",
let constant numbers:IntVec = {IntVec 1,2,3,4}
There is no equivalent for
def (name1, name2) = values
since there is no syntax for computing the compile-time type of a multi-value expression, but the principle is the same allowing you to write code like:
def (val, n-consumed) = {string.to-int}
Although, def declarations may specify explicit types, there is usually no reason to do so.The following example demonstrates the difference between the implicit types of the let and def syntaxes. The variable defined using let will have an implicit type of any, while the variable defined using def will have the type #String, since that is what the hello proc is declared to return. The actual runtime type of the value will be String in both cases.
Example: Implicit variable types with let and def
{define-proc {hello}:#String
    {return "hello"}
}

{value
    def def-s = {hello}
    let let-s = {hello}
    
    {format
        "def-s type is '%s', let-s type is '%s'",
        {compile-time-type-of def-s},
        {compile-time-type-of let-s}
    }
}

Global and Local Variable Differences

Summary:
  • In top-level code, a variable definition can access a variable that is defined later in the same let expression.
  • In a code block, a variable definition can only access variables that are already defined.
The let expression has slightly different scoping characteristics in top-level code and within a code block. In top-level code, a variable definition can access a variable that is defined later in the same let expression. However, in a code block, a variable definition can only access variables that are already defined. For example, the following declaration is legal in top-level code but illegal in a code block:
{let
    a:int=b,
    b:int=0
}
The following code shows a let expression within a procedure code block that generates a syntax error:
{define-proc public {qwerty}:int
    let a:int=b,
        b:int=0
    {return a}
}

{qwerty}


SyntaxError: 'Attempt to access b inside its initializer.'

Using a Variable in its Own Definition

Summary:
  • The Curl language binds the value of expressions to variables in a manner familiar to most developers.
  • However, it creates anonymous procedures without executing the body of the procedure itself.
  • This means that you can refer to the variable being defined within the anonymous procedure.
The Curl language has adopted some very powerful programming features from the Lisp and Scheme programming languages. C++ and Java™ programming language developers may not be aware of the full implications of these features. One such programming feature is the ability to create anonymous procedures in the middle of other code (in other words, not at the top level). Understanding and applying these programming features correctly can provide tremendous flexibility and power to Curl language developers.
The Curl language binds expressions to variables in a manner familiar to most developers. If you use a let statement to declare and initialize a variable, like most programming languages, the runtime evaluates the expression that initializes the variable. This, of course, means that you cannot use a variable in its own definition. For example, the following code throws an error:
{value
    let x:int = x + 1
}


SyntaxError: 'Attempt to access x inside its initializer.'
However, under certain circumstances, you can refer to a variable within its definition. In particular, you can refer to a variable when the reference to the variable is within an anonymous procedure. (Anonymous procedures are also known as procs and are described in the Procedures chapter.) The value of an anonymous procedure definition is the procedure itself. The body of the anonymous procedure is not executed when the procedure is defined, but when the procedure is used. Because the runtime binds the anonymous procedure to the variable before executing the body of the anonymous procedure, you can refer to the variable within the anonymous procedure. This has many useful applications.
One feature that makes implicit use of anonymous procedures is the on expression for specifying event handlers. This means that the on clauses in a variable definition can refer to the variable being defined, allowing you to perform tasks such as modifying the item being defined. For example, the following code declares a button that changes the test color when clicked. The on expression in the button declaration includes a reference to the button.
The example first declares a variable b with a data type of CommandButton and initializes b with an instance of the CommandButton class that changes its text color to red when clicked. To do this, the action clause of the button declaration refers to the variable being declared.

Example: Using a Variable in its Own Definition, Part 1
{let b:CommandButton =
    {CommandButton label="I like red",
        {on Action do
            set b.color = "red"
        }
    }
}

|| Display "b".
{value b}
By allowing you to create an anonymous procedure before executing the procedure's body, the Curl language provides you with a very powerful programming feature. However, in order to use this programming feature correctly, you must be aware of the value of the expression when it is finally executed. This issue can be particularly complicated when different anonymous procedures are created on each iteration of a loop, but with each procedure referring to the same variable defined outside the loop. The remainder of this section provides examples that illustrate these issues.
The following example shows a for loop that, for each iteration through the loop, adds the loop index variable to a VBox. The VBox contains the value of the loop index variable for each iteration.

Example: Using a Variable in its Own Definition, Part 2
{value
    || Declare and initialize a VBox "message".
    let message:VBox = {VBox}

    || For each iteration through the loop, add the loop
    || index to "message".
    {for index:int = 0 to 3 do
        {message.add index}
    }

    || Display "message".
    {value message}
}
The next example, however, probably generates output that you do not expect. For each iteration through the loop, this example adds a button to a VBox. The labels on the buttons include the value of the loop index variable. When you click on a button, this example adds the value of the loop index variable to the display of another VBox. However, this example illustrates an unexpected consequence of the way that the runtime binds anonymous procedures to variables within a loop when the expression contains a variable that is declared outside of the scope of the loop.
The runtime binds the on expression of the CommandButton declaration without evaluating the expression. When you click one of the resulting CommandButtons, the runtime evaluates the on expression, which adds the loop index variable to the VBox. However, the loop has terminated and the loop index variable is set to the value that caused the loop to finish executing. The value of the loop index variable that terminated execution of the loop is added to the VBox regardless of the CommandButton that is clicked.

Example: Using a Variable in its Own Definition, Part 3
{value
    || Declare and initialize two VBoxes; "buttons" and
    || "message".
    let buttons:VBox = {VBox}
    let message:VBox = {VBox}

    || For each iteration through the loop, add a CommandButton
    || to "buttons".
    {for index:int = 0 to 3 do
        || If you click on the CommandButton, add the loop index
        || to "message".
        let b:CommandButton = {CommandButton label="index is... " & index,
                                  {on Action do
                                      {message.add index}
                                  }
                              }
        {buttons.add b}
    }

    || Display "buttons" and "message".
    {VBox
        {value buttons},
        {value message}
    }
}
To obtain the intended result, create a variable within the scope of the for loop that will hold the value of the loop index variable and use this variable in the on expression. Because the variable is declared within the scope of the for loop, a copy of the variable will be maintained for each iteration through the loop. Then, when the on expression is evaluated, the runtime will use the appropriate copy of the variable for that CommandButton.

Example: Using a Variable in its Own Definition, Part 4
{value
    let buttons:VBox = {VBox}
    let message:VBox = {VBox}

    {for index:int = 0 to 3 do
        || Declare an int "temp" and initialize it to
        || the value of "index"
        let temp:int = index
        || If you click on the CommandButton, add the
        || value of "temp" to "message".
        let b:CommandButton = {CommandButton label="index is... " & temp,
                                  {on Action do
                                      {message.add temp}
                                  }
                              }
        {buttons.add b}
    }

    {VBox
        {value buttons},
        {value message}
    }
}
To further illustrate this issue, consider the situation where the variable is instead declared outside of the for loop, but updated for each iteration within the loop. Again, because this example evaluates the on expression when the CommandButton is clicked, the current value of the variable is added to the display of the VBox (and not the value of the variable when the CommandButton was created).

Example: Using a Variable in its Own Definition, Part 5
{value
    let buttons:VBox = {VBox}
    let message:VBox = {VBox}
    || Declare an int "temp"
    let temp:int

    {for index:int = 0 to 3 do
        || Assign the value of "index" to "temp"
        set temp = index
        let b:CommandButton = {CommandButton label="index is... " & temp,
                                  {on Action do
                                      {message.add temp}
                                  }
                              }
        {buttons.add b}
    }

    {VBox
        {value buttons},
        {value message}
    }
}