Amigo syntax for denoting fields and methods of class is a bit unusual. More often keywords var and fn are used to denote declarations for local and global variables and functions. But this syntax was not chosen by chance. The thing is that classes in Amigo are just functions. All OOP constructions derived from concepts first-class functions and closures in Amigo. In doing so, Amigo introduces an expanded concept of function.
Let's consider an example:
fn A() { var u = 10; var v = 20; fn B(w) { return [u, v, w]; } return B; } var c = A(); var x = c(30); println(x); // [10, 20, 30]
In terms OOP, A function can be considered as a class, function B - constructor of objects of class A. Being called, A returns closure B (i.e. constructor) and saves it in the variable c. Then constructor is called with parameter 30 and returns instance of class, variable x saves it.
This example can be rewritten in Amigo as:
class A { var u = 10; var v = 20; fn constructor (w) { return [u, v, w]; } } var c = A(); var x = c(30); println(x);
We can hide the explicit constructor call with the new keyword. The following lines are equivalent to lines above:
class A { var u = 10; var v = 20; fn constructor (w) { return [u, v, w]; } } var x = new A(30); println(x);
Now we can give the following notes. In Amigo:
However, the explicit return of object of class in constructor body is not a common practice in Amigo. It is more native to rewrite the class definition A as:
class A { var u = 10; var v = 20; var w; fn constructor (w) { this.w = w; } }
Constructor obtains outer variables as a tuple and clone of this tuple is returned by constructor. These outer variables are just local variables of outer function, i.e. class.
Let's add a method to the class definition:
and give one more note. In Amigoclass A { var u = 10; var v = 20; var w; fn constructor (w) { this.w = w; } fn Norm() { u * u + v * v + w * w; } }
We want to have methods be visible outside of class definition. This is the first extension of the concept of function in Amigo. In other words, we want to have possibilty to write:
var x = new A(30); println(x.Norm());
Before start of program execution, Amigo runner creates closures for all nested functions (i.e. methods) of classes. This the second extension of the concept of function in Amigo. Despite the fact that function Norm was never explicitly returned by function A, Norm catches its lexical environment, i.e. list of outer variables, that's list of local variables function A. Content of this list is not important at this moment. Important only that Norm reserves space for this list in the heap. Each nested function keeps this list as a tuple in its internal representation. Therefore, in principle, we can access to this list using identifier Norm as a tuple. Let's name this tuple of a nested function as closure list.
When object x calles Norm, it replaces the closure list of Norm with content of x. Thus Norm operates with content of x during its call.
Internally, each tuple is represented as a pair (descriptor, content). Descriptor contains 2 values: Address and Length. The Address value contains the address of tuple elements (more exactly - address of the first element of tuple) in the heap. The Length value contains a number of tuple elements.
Thus, when Norm is called, the destrictor of x replaces the descriptor of Norm. It is important to note that descriptors of closure list of Norm and descriptor of x have equal values of Length a priori.
So, we have considered that Amigo introduces the concept of the class. Now let's look at how the concept of OOP inheritance works in Amigo.
To add the inheritance for classes, we introduce this concept for the Amigo functions first. This the second extension of the concept of function in Amigo.
We say that function G inherits F, when all members of F are included into the scope function G.
Let's consider a demo:
fn Point() { var X, Y; fn Norm() => X*X + Y*Y; fn Ctor (x, y): Point { X = x; Y = y; return [X, Y]; } return Ctor; } fn Circle() <- Point { var R; fn Ctor (x, y, r): Circle { Point::Ctor(x, y); R = r; return [X, Y, R]; } fn Norm() => Point::Norm() + R*R; return Ctor; } var c = Circle()(2, 3, 4); println(c); println(c.Norm());
Function Circle inherits function Point. Type Circle is included in the definition of Ctor nested function to have correct translation of call c.Norm(). X and Y are not declared in the Circle function explicitly. They are assigned after call of Point's Ctor nested function.
Using notation for Amigo classes, the example above can be rewritten in more compact way as:
class Point { var X, Y; fn Norm() => X*X + Y*Y; fn constructor (x, y) { X = x; Y = y; } } class Circle: Point { var R; fn constructor (x, y, r) { base(x, y); R = r; } fn Norm() => base.Norm() + R*R; } var c = new Circle(2, 3, 4); println(c); println(c.Norm());
The base keyword is used to call the inherited methods.
Summarize, we can see that Amigo uses the following approach to the OOP. If an OOP feature is necessary, it is introduced for functions first, then it spreads to classes as well. By this way, Amigo introduces visibility for class members (public, protected private, and public is default visiblity), static members of classes, generic types, operator overloading. Not only classes are derived from functions in Amigo. All supported types are derived from functions: interfaces, enums, ranges.
For example, declaration
has function MyEnum in the background:enum {Red, Green, Blue}
so, the following code is legal:fn MyEnum() { const Red = 0; const Green = 1; const Blue = 2; null; }
println(MyEnum.Green);
At the end, let's consider how the concept of OOP polymorphism works in Amigo.
Each class (function) in Amigo has run-time information (RTI). The RTI includes: tuple of offsets of local variables, address of ToString method for this class, tuple of addresses of ancestors for given glass, etc. In particular, the RTI includes VMT for virtual methods of class. As usual, VMT is being built during compile-time. For simplicity, methods of interface types are considered as virtual ones and accessible via VMT too.class A { var P, Q; virtual fn Test() { println("I am A"); } } class B: A { var X, Y; override fn Test() { println("I am B"); } } var Z: A = new B(); println(Z is B); // true Z.Test(); // I am B