Object is the minimum unit of data in Quail. Object always contains a table, metadata and could contain some value.
Table is a (string) key => (Object) value structure
Object metadata is a collection of different values that determinate Object’s behaviour. These include:
Prototype is an object that replaces classes. Defines fields and methods. Can also be referred as class.
Parent prototype is a prototype from which the object was derived
Derivation is an operation of making a new object in the likeness of prototype. Corresponds to instantiation in class-based OOP.
Super prototype is a prototype from which the given prototype was extended
Prototype extension / inheritance is an operation of making a new prototype in the likeness of super prototype. Corresponds to extension in class-based OOP.
Constructor is a special function that is called right after derivation and initialises the object
Fields and methods of a given object are stored in its table.
They can be accessed with dot notation: myObject.myMethod(myObject.myField)
New fields can be added, and existing modified dynamically:
myObject.myExistingField = 2
# No need to somehow explicitly create new field
myObject.myNewField = "Hello, this is a new value"
A quite important note: Dictionaries in Quail override this dot notation, so you can access theirs values via it too:
d = {"a" = 1, "b" = 2} print(d.a)
Methods are just fields that contain functions. Thus, dynamic creation and modification applies to them:
myObject.myExistingMethod = (this, args...) -> {}
myObject.myNewMethod = (this, args...) -> {}
Same rules apply to prototypes. And these changes affect extended prototypes and theis derivatives:
# assuming B extends from A:
a = A()
b = B()
A.someNewMethod = (this, args...) -> {}
# Even after creation, new methods and fields could be accessed
b.someNewMethod(arg1, arg2)
All objects have shadow fields that are not in the table and cannot be modified, but can be accessed:
myObject._className
myObject._superClass
myObject._objectPrototype # object's prototype
myObject._isPrototype
myObject._isInheritable
Class creation
class A {
field1 = "some value"
constructor(this, args...) {}
method method1(this, args...) {}
static method method2(this, args...) {}
}
Class extension
class B like A {
# some additional functionality goes here
# ...
}
As was written in 6.1 objects can have their own values. Number
is an example of this:
public class QNumber extends QObject {
// ...
protected double value; // This is the value
// ...
public QNumber(double value) {
this(new Table(), prototype.className, prototype, false);
this.value = value;
}
// ...
}
Thus, any Number
can be treated in two different ways at once: as an object and as a number:
number = 3.14
number.myField = "some value"
print(number + 1)
And using dynamic method addition, we can extend default values with additional functionality:
Number.square = (this) -> {
return this ^ 2
}
print(4.square())
When extending class with values, these values are not lost: (see 6.Appendix I)
class MyNumber like Number {
constructor(this, value) {
# more info in Standard library Chapter
reflect.setNumberValue(this, value)
}
method square(this) {
return this ^ 2
}
}
a = 3
b = MyNumber(2)
print(3 + b.square())
# will print 7
When a function is called via field access (obj.f()
) it is treated as a method call. Only exceptions are:
obj
is a prototypeobj
is a dictionary and it contains f
as a keyf
is a static function
If at least one of these conditions match, then it is treated as a function call.The only difference between function and method call is that in method call, reference to the object is put into arguments:
# this is how you see a method call
obj.method1(arg1, arg2)
# this is how interpreter sees it
obj.method(obj, arg1, arg2)
So if method to be extracted from object and called separately, it will be treated as a function call and in order to make it function correctly, you should manually pass the reference:
method1 = obj.method1
method1(obj, arg1, arg2)
Constructor in objects is always named _constructor
.
When new object is being initialised, it is firstly derived from prototype, then initialised with constructor:
public class QObject {
// ...
public QObject newObject(Runtime runtime, List<QObject> args, HashMap<String, QObject> kwargs)
throws RuntimeStriker {
QObject blank = derive(runtime);
blank = blank.callFromThis(runtime, "_constructor", args, kwargs);
return blank;
}
// ...
}
Constructor receives reference to initialising object as first argument, then all the other args:
class A {
constructor(this, args...) {}
}
Value which constructor returns is ignored.
When Quail class is defined in Java as a Java class also (e.g. as QNumber
in example above), prototypes that
extend from it remain to be this particular Java class:
class MyNumber like Number {
constructor(this, value) {
# more info in Standard library Chapter
reflect.setNumberValue(this, value)
}
method square(this) {
return this ^ 2
}
}
a = 3
b = MyNumber(2)
print(3 + b.square())
# will print 7
From Java perspective, MyNumber
prototype is an instance of QNumber
Java class.