Cobra supports the normal range of variables
- Local Variables (lexically scoped)
- Instance Variables
- Method parameter Variables
- 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 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 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 get if not _definition return 'No Defn' return _definition.name set assert value.name _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') #... t.rename('hopscc')
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 = t.name # local var oldName <- 'hops' t.name ='hopscc' t.defn = .makeDefn(t) print t.repr # prints 'hopscc_492'
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.