Cobra supports the normal range of variables

  • Local Variables (lexically scoped)
  • Instance Variables
  • Method parameter Variables
  • Properties
  • Class or shared/static Variables

Variables are statically typed but typing can be done explicitly (and optionally) with a declarative "as clause" otherwise the variables type is inferred from initialization or first assignment or use. Static typing allows cobra to be fast in execution, Type inference makes static typing bearable.

Variables are normally 'early bound' - typed at (first) use or declaration.
Variables can be dynamically typed (untyped) or 'late bound' by explicitly giving them (or for some variables allowing them to default to) the dynamic type.

Local Variables

Local variables are created and used within a method.
They are used for temporary storage or calculation and are named starting with a leading lowercase letter (no leading underscores or punctuation).

They come into existence on first assignment unless explicitly declared but are created local to the method they appear in (not just the block they might have been first used in).
A compiler error will be generated if a local variable is used (read) before it is created (initialized).

Local variables can be explicitly typed if desired otherwise they infer their type from the type of what they are assigned from.

s = "name:" # s is a String since thats what it is assigned from
greet = 'hello ' + 'Mike.' # also a String since the assignment is a (String) expression

i = 99 # i is an int (int32)  since thats what 99 is
iu = 99_u8    # iu is an unsigned 8 bit int from the literal suffix on 99_u8

s1 as String = 'xxx' # redundant 'as TYPE' clause since assigned from a String anyway

i1 as int            # declaration without initialisation - defaults to the default value for the type (0 in this case)  

Local variables of Reference types should generally be assigned to, to create and type them.

     s as String  # generates a compiler error   
     # needs to be
     # s as String?
     s = 'string value'
     print s

Non-primitive (Reference) typed variables, if declared and not initialised
need to be declared as nilable since they will default to the default-value for
a reference type which is nil (null) and if they're not nilable they can't have a nil value.

Idiomatically in cobra it's best to create local variables by first use (an assignment)
rather than an explicit type declaration.

Sometimes though you need a variable to be typed differently (usually wider) than what it is
assigned from. To do this you can explicitly type the variable with an 'as clause' on first
use or cast (upcast) the initial value to the desired type.

space = 32_u8
ispace as int32 = space
# or
ispace = space to int32

enclosure as Shape = Circle()
# or
enclosure = Circle() to Shape

Instance Variables

Instance variables are variables associated with a Type (commonly class) instance.
They encapsulate the state of the instance object
They must be declared (using the var keyword in a Type (class/structure/interface) definition) but they too can be explicitly typed or have their type inferred from being initialised in the declaration.
Like method parameter variables, if untyped and uninitialised, their type defaults to type dynamic.

Instance variables default to public access.
If named with a leading '_' they default to protected access, with double leading '_' to private access.
Accessibility may be explicitly specified otherwise or overridden with an 'is clause' specifying one of 'public', 'protected', 'private' as per the usual variable AccessModifiers.

class VarEG
    var count = 0  # type inferred from initial value (int32)
    var age as int # explicitly typed to int (int32) initial value default value for int (0)

    var _name as String # explicitly typed to String, protected access. must be initialized
    var _definition as INamedNode?
    var other is private # dynamic type, explicitly private

    # Properties    
    pro defn as String  # String Property
            if not _definition
                return 'No Defn'
            _definition = value    

    pro name from _name     # Read-write String Property from _name 

    get repr  as String     # Read-only String property - synthesized
        return '[_name]_[.age]'        

    # class Variable
    var nItems as int is shared

Unlike local variables, explicitly typed instance variables without an initial value given need not be declared nilable unless they are intended or allowed to have a nil value after the class instance initializer has run.
Instance variables that are not nilable will emit a runtime error if they have a nil value at the end of the initializer execution.

In code, instance variables are accessed with a leading 'this.' or just '.'.
(idiomatically and preferably just a leading '.').
The leading '.' is unnecessary if the instance variable name has a leading '_'.
(These are unambiguously instance variables as local variables cannot be declared with leading underscores.)

    # Initializer/Ctor
    cue init( age as int, name as String)
        .age = age 
        _name = name    # ok _name now initialised and non nil
        .other = .calcOther(name, age) 
        .nItems += 1
    # A method
    def rename(nuName as String) 
        .count += 1  # instance variable
        calcName = nuName + '_[.count]'   # local variable (String)
        .name = calcName    # change instance variable

By the end of the initializer, instance variable _name is fully initialised,
_definition (VarEg?._definition) is nil but thats allowed since its declared nilable.

Method parameter variables

Methods (initializers, etc) in a class may be declared to take parameters.
These can be optionally explicitly typed. If method parameters are not explicitly typed they are assumed to be the dynamic type.

Method parameter variables are named the same way as local variables ( leading lowercase letter) and are also local only to the method they are on.
Calls to the methods convey the arguments given in the call to the (formal) parameter names in the order declared on the method for use in the called method.

    # call to create VarEg instance
    t = VarEG( 157, 'hops')

In the initializer call parameter variables

  • age <- 157
  • name <- 'hops'

in method call rename, parameter variables

  • nuName <- 'hopscc'


Properties are a construct for external change or access to a classes state (class or instance variables) mediated by some code.
They can be a simple passthrough to a possibly otherwise inaccessible class variable (field) or anything else doable in a method.

The major difference/advantage is that to calling code they are written to appear exactly like a class variable access or mutation (no punctuation or boilerplate for method calls) so all the description for instance variables applies.

    t = VarEG( 492, 'hops')
    oldDefn = t.defn    # local var oldDefn <-  'No defn'
    oldName =    # local var oldName <- 'hops' ='hopscc'   

    t.defn = .makeDefn(t)
    print t.repr        # prints 'hopscc_492'    

Class Variables

Class (or shared or static) variables are like instance variables but they are associated with the class rather than each instance of a class. They are declared like instance variables except that they have the modifier 'shared'. Class variables can be accessed through a class instance or using the className otherwise all the description of instance variables applies.

    # Note that nItems is incremented in the initializer so it has a count of the 
    # number of instances made
    nItems = VarEG.nItems
    print 'number Of VarEg instances made ' + nItems
    nullVarEG = VarEg(0, '')
    assert  nullVarEG.nItems == nItems + 1 # access class vars through instance or className

These variables can also be created subject to the usual AccessModifiers for variables.


See Also

Access Modifiers

Back to LanguageTopics