Classes
The UltraGen types interface
As said before, UltraGen is a primarily object oriented language - even it has some functional capabilities and is able to be used in a procedural style. Every value in UltraGen is an object. The types are interfaced to the developer by the keyword "class".
Note
The words class and type will be used interchangeably and refer to the same feature.
Syntax
To create a class you just need to declare it with the class keyword and the name of the class which must follow the UltraGen valid name rule.
class MyClass
"Where's the block?"
Different from other languages UltraGen syntax doesn't define a block for declaring the attributes of your class. From now on the syntax for declaring class components will be presented.
Full example
For this feature, a full explained example will fit better for illustrating classes declaration.
# 1
class MyClass
# 2
function init(arg1, arg2) : MyClass self
self.otherAttr = "some attribute"
end
# 3
function someStatic() : MyClass
print("some static")
end
# 4
function someInstance() : MyClass self
print("some instance")
print(self.arg1)
print(self.arg2)
end
# 5
MyClass.someVar = 99
# 6
MyClass.someFunc = function()
print('anonymously assigned')
end
# 7
# instance use
myClassInstance = new MyClass(0, 1) # executes init constructor
myClassInstance.someInstance() # prints "some instance", "0" and "1"
# static use
MyClass.someStatic() # prints someStatic
MyClass.someFunc() # prints "anonymously assigned
print(MyClass.someVar) # prints "99"
1 - Declaring a class
The class keyword defining a new type/class.
2 - The constructor block
An optional constructor. If not provided, a default constructor with no statements and no arguments is used. If provided, declared parameters is assigned to instance attributes automatically. In this example, arg1
and arg2
will be available as instance attributes of object. The usual "class block" that other languages has for defining attributes in UltraGen is the constructor. This is the block where you define the instance attributes of a class as done with otherAttr
.
The constructor declaration must be followed by the keyword self
indicating that the method is an instance method.
3 - Declaring a static method
The syntax for declaring a static method is very alike a regular function. The difference is you must write a colon and the class to whom that method belongs.
4 - Declaring an instance method
The syntax for declaring an instance method is very alike a regular function. The difference is you must write a colon and the class to whom that method belongs and the self
keyword after the class name. This tells to interpreter to provide a reference to the own object in the function block accessible through the self
keyword.
5 - Declaring static attributes
As class declarations does not has blocks, you declare static attributes as regular variables but with a reference to the class where they belong. As with methods, this can be done in any file. You just must guarantee that all the values you need are available
6 - Declaring static methods with anonymous functions
As a static variable can receive any value, this value can also be an annonymous function. The syntax is the same for static variables. Only static methods can be declared this way.
7 - Using class/instance methods/attributes in application
For calling methods from class you just need to call them preceeded by the name of the class or the instance according to use. As with regular functions it's possible to chain method calls.
About UltraGen object orientation
After explaining the syntax for dealing with classes, let's talk about class charateristics in UltraGen.
Declare methods/static attributes apart from class declarations
It can be done in any file, in any part of application. You only must guarantee that when the file which has the method declared is executed the reference to the class exists.
Example:
MyClass.ultra
class MyClass
otherFile.ultra
function myFunc() : MyClass
print("I'm in another file")
end
main.ultra
include 'otherFile.ultra'
# will raise an error
# as MyClass does not exist
How to fix this?
class MyClass
include 'otherFile.ultra'
main.ultra
include 'MyClass.ultra'
MyClass.myFunc()
# no errors. When 'MyClass.ultra' is included, it includes 'otherFile.ultra' without errors
# When 'otherFile.ultra' is executed, the class MyClass is already declared
While this may seem a little bit complicated, remember it's not much different from .pp
or .inc
files featured in pascal, where you can declare parts of a unit in another file and the files are included during the compilation process.
UltraGen class features
At this time (and it's something that may change in future) UltraGen does not feature the most of ideas behind OOP. UltraGen does not feature abstract classes or methods, interfaces, class inheritance, traits and access modifiers.
The first reason for this is because at the OOP point UltraGen is more near from Python, giving the developer the freedom to handle these questions at his own responsibility. That is why it has no private or protected access modifiers.
The second is that for the purpose UltraGen is mainly built (process views from provided data) this is enough. It's the minimum required for a decent functionality organization. However, some of these features may appear in the interpreter at some time but they are really not a priority now.
Now it's time for tricks
Even UltraGen does not feature these functionalities, some of them may be emulated using some technics. From now on we'll show some of them.
Inheritance
Inheritance may be emulated by declaring a function which takes a class (parent), add some functions to it (child) and returns an instance of the "improved" type with new functions declared.
The example shows how this works
class Parent
function fromParent() : Parent self
print('from parent')
end
function ChildOne()
Klass = Parent
function fromChild() : Klass self
print('from ChildOne')
end
return new Klass()
end
function ChildTwo()
Klass = Parent
function fromChild() : Klass self
print('from ChildTwo')
end
return new Klass()
end
co = ChildOne()
ct = ChildTwo()
p = new Parent()
co.fromChild()
ct.fromChild()
co.fromParent()
ct.fromParent()
co.fromChild()
ct.fromChild()
This code has the following output
from ChildOne
from ChildTwo
from parent
from parent
from ChildOne
from ChildTwo
Obviously, the bad thing about this trick is that the functions of children classes will be declared every time a new object is instantiated. Our best suggestion for overtaking this lack of feature is to use composition instead of inheritance.
class Parent
function someFunction() : Parent self
print('simulating a super call')
end
function otherFunction() : Parent self
print('calling other function')
end
class Child
function init(parent) : Child self
end
function someFunction() : Child self
print("I'll call my 'parent'")
self.parent.someFunction()
end
parent = new Parent()
child = new Child(parent)
child.someFunction()
child.parent.otherFunction()
This prints:
I'll call my 'parent'
simulating a super call
calling other function