Classes and Objects
This page discusses programming classes and objects in Slag. See also: Understanding Object-Oriented Programming.
A Simple Class
class Counter
PROPERTIES
count : Int32
METHODS
method init( count=0 ):
method click:
count++
method to_String.String:
return "" + count
endClass
# Test code (try by placing in init() of main class)
local Counter c()
println( c ) # prints: 0
c.click
c.click
println( c ) # prints: 2
c = Counter(5)
println( c ) # prints: 5
c.click
println( c ) # prints: 6
- Member Categories are used to group object members appropriately. Object variables and methods are defined under PROPERTIES and METHODS headings. Class variables and methods (e.g. "static") are defined under CLASS_PROPERTIES and CLASS_METHODS.
- Constructors are named "init". They are called automatically when an object is created; they can also be called by one another or at any time from outside code.
- Slag's properties are the same as instance variables and fields in other languages.
- Parameters can auto-initialize member vars. Leaving the type off of a method parameter indicates that the parameter is an auto-initializing parameter and should be automatically stored into the property of the same name. Parameter and local variable names cannot otherwise "shadow" (i.e. be named the same as) properties.
- Local variables declarations are preceded with 'local'.
- Default parameters can be specified. A method parameter can be of the form "Int32 n = 3", meaning that if the argument is omitted by the caller, the value "3" will be inserted automatically. Note that the default value has to make sense from the caller's context, not the context of the class in which the method is defined.
Their true type is then inferred from their initial assignment.
- Object creation requires () but not "new". To create an object of a type, write *ClassName([params])*. No "new" keyword is required, but you must at least have empty parentheses.
- "local Counter c()" is shorthand for "local Counter c = Counter()". Likewise for properties, "c() : Counter" is shorthand for "c = Counter() : Counter". Writing "local Counter c" is equivalent to "local Counter c = null".
Transparent Access Methods
class Temperature
PROPERTIES
fahrenheit : Real64
METHODS
method init:
method celsius( Real64 n ): fahrenheit = (9.0/5.0)*n + 32.0
method celsius.Real64: return (5.0/9.0)*(fahrenheit-32.0)
method kelvin( Real64 n ): celsius = n - 273.15
method kelvin.Real64: return celsius + 273.15
method to_String.String:
return "$(.2)C / $(.2)F / $(.2)K" (celsius,fahrenheit,kelvin)
endClass
# Test code
local Temperature freezing(), boiling()
freezing.celsius = 0
boiling.fahrenheit = 212
println( "Freezing: " + freezing )
# prints: Freezing: 0.00C / 32.00F / 273.15K
println( "Boiling: " + boiling )
# prints: Boiling: 100.00C / 212.00F / 373.15K
- In Slag's philosophy, data can be stored or retrieved by object members but generally it doesn't matter whether those members are properties or methods - not having to know is a useful abstraction.
- In such cases a method of the form "method name( DataType value ):" is called a property-set method. Code of the form "name = value" will be transformed into the method call "name(value)".
- A method of the form "method name.DataType:" is called a property-get method. Writing code of the form "println(name)" will be transformed into "println( name() )".
- Property get/set methods are collectively referred to as transparent access methods. They allow you to access data without knowing whether the data is stored in a property or is computed on demand.
Direct Property Access and Primitive Methods
class Completion
PROPERTIES
percentage : Real64
METHODS
method init( percentage=0.0 ):
method percentage( Real64 new_p ):
&percentage = new_p.clamped(0.0,100.0)
method to_String.String:
return "$(.0)%" (percentage)
endClass
# Test code
local Completion progress(50)
println( progress ) # prints: 50%
++progress.percentage
println( progress ) # prints: 51%- Transparent access methods can guard access to properties. Within an access method for property "x", use "&x" to refer to the property directly and "x()" to refer to the property access method.
- Object members are generally "public". The primary reasons to forbid direct access to variables (as in C++ and Java) are to promote implementation independence and control access to the object members via get/set access methods. Slag sidesteps this problem entirely with its transparent access methods and so members are encouraged to be 'public' by default.
- A call to "a_primitive.method_name(args)" is automatically converted by the compiler to be "method_name( a_primitive, args)". In the example above, clamped(n,low,high) is a method that may be found in the global scope.
Automatic Initializer Methods
This class:
class Counter( Int32 count=0 )
method click:
count++
method to_String.String:
return "" + count
endClass
is equivalent to the longer Count class shown above.
- Writing a parameter list after the class name automatically creates an init() method with those parameters.
- An auto-initializer parameter including a type ("Int32 count") creates a property of that name and type.
- An auto-initializer parameter that does not include a type ("count") will generate a property assignment but will not actually create the property itself.
"Static" Class Members
class Counter( Int32 count=Counter.default_count )
CLASS_PROPERTIES
default_count=0 : Int32
CLASS_METHODS
method reset_default_count:
default_count = 0
METHODS
method click:
count++
method to_String.String:
return "" + count
endClass
# Test code
local Counter c1()
local Counter c2(3)
Counter.default_count = 7
local Counter c3()
Counter.reset_default_count
local Counter c4()
println( "$, $, $, $" (c1,c2,c3,c4) ) # prints: 0, 3, 7, 0
println( c3.default_count ) # prints: 0
println( Counter.default_count ) # prints: 0
- Class properties and methods in Slag are analogous to static variables and methods in Java.
- While every object has its own copy of regular object properties, all objects of a class share the same values of class properties. Another way to think of it is that class properties are like global variables that belong to a certain class name - and the same goes for class methods.
- Class properties and methods may be accessed through an object of the class or simply through the class name itself.
- Technical note: class member accesses may only be performed via the class name or on simple object references - not on expressions that could have side effects. For example, you can't refer to "Counter().default_count" because creating an object has a side effect (of - well, of creating an object). The reason for this restriction is that any such context is discarded by the compiler rather than being compiled in to the executable.
Extended Classes
- Syntax: "class Spruce : Tree".
- Each class extends exactly one other base class.
- If no base class is specified, a class extends class Object by default.
- If no 'init' constructor methods are defined, the existing init() methods are inherited from the base class.
- Use the syntax prior.method_name instead of Java's super.method_name to call the overridden version of a method instead of the current version. This applies to constructors as well (prior.init).
- As in Java, the type of the reference variable determines what methods can be called. The type of the object itself determines the version of the method that is called (single dynamic dispatch). See also: multimethods.
- Abstract methods are defined by putting the keyword "abstract" in place of any other method body.
- Use the operators instanceOf and notInstanceOf to determine if an object is at least a certain class (and possibly more).
Type Qualifiers
Classes and aspects may have the following type qualifiers (e.g. "requisite singleton class Hero"):
| Qualifier | Allowed In | Description |
| abstract | classes | Prevents objects of this class from being created. Useful when a class is intended to be a base class. |
| requisite | both | Indicates that this type is required by the native layer. Prevents a type that's never directly used from being culled by the compiler. |
| singleton | classes | Indicates that a single global instance of the given class should be created. The singleton is created at load time. See the section on Singletons. |
| managed singleton | classes | Indicates that the actual implementation of a singleton (e.g. "World") should be managed by Slag-side code (e.g. "WorldManager"). See the section on Singletons. |
| deferred singleton | classes | Creates a managed singleton with default behavior. The singleton object is not created until it is first accessed. |
| underlying | aspects | Makes all methods in this aspect underlying unless otherwise specified. |
| overlaying | aspects | Makes all methods in this aspect overlaying unless otherwise specified. |
Property Qualifiers
Properties can have the following qualifiers (e.g. "x : private Int32"):
| Qualifier | Description |
| public | Objects of any other class may directly access this property. |
| private | Same as "protected" in C++. Only objects that are instanceOf this property's class may directly access this property. |
| readOnly | public read, private write |
| writeOnly | private read, public write |
| delegate | Designates this property as an additional context for call resolution. If some object "obj" has a delegate property "backer", then if the compiler fails to resolve "obj.fncall()" then it will automatically attempt to call "obj.backer.fncall()" instead. |
| abstract | Informs the compiler that this property will exist but that it is defined separately. As an example, if you knew that a separate aspect was going to impart a property called "x" and you wanted to use x as an auto-initializer ("init(x)"), you could declare "x : abstract Int32". |
Technical Notes
- Initial value assignments for regular properties are placed in a special method called "init_object()". You can define your own init_object() method; if you do, the compiler will still place initial value assignments in that method before your custom instructions. When an object is created, the init_object() method is called on it first and then the regular init(...) is called.
- Initial value assignments for static properties are placed in a special class method called init_class() that is invoked at the beginning of your program. Be careful accessing class properties of other classes from class methods - there's no guarantee what order class initialization will be performed in.
Method Qualifiers
Methods can have the following qualifiers (e.g. "private method do_something( Real64 n, Logical setting ):"):
| Qualifier | Description |
| public | Objects of any other class may call this method. |
| private | Same as "protected" in C++. Only objects that are instanceOf this method's class may call this method. |
| abstract | Allows a method to be declared without being defined. The actual definition is deferred to an aspect or an extended class - see the sections on Extended Classes and Aspects. "abstract" may be written either before the method keyword or after the colon in place of the method body, e.g. "method print( Char ch ): native". Objects may not be created of a class that has abstract methods. |
| native | Indicates that the actual implementation of this method is performed by the native layer. Like abstract, "native" may be written either before the method keyword or in place of the method body. |
| limited | Somewhat like "private" in C++ - a limited method is not visible to extended classes. However it may be called by writing "prior.method_name" in an extended class. |
| requisite | Indicates that this method is required by the native layer. Prevents a method that's never called from being culled by the compiler. |
| underlying | Designates a method as an underlying layer during aspect method composition (aka "code weaving"). See the section on Aspects for more information. |
| overlaying | Designates a method as an overlaying layer during aspect method composition. |
