load help; @[name] dao.class @[name] @[title] Class for Object-Oriented Programming @[title] @[text] Class is a user-defined data structure that supports data abstraction, encapsulation, polymorphism and inheritance etc. A class data strucutre is defined by composing data fields and member methods in a meaningful way that defines the states and behaviours for the instances of the class. It is commonly used to do Object-Oriented Programming (OOP). @[text] @[name] dao.class.definition @[name] @[title] Definition @[title] @[text] Classes are defined using the @[code]class@[code] keyword, followed by a class name and base/parent classes if any, and then the class body. The following is the simplest class: @[code] class SimplestClass { } @[code] which has no base classes and contains no member fields. In order for the class to have meaningful use, it must contains some fields to define the states or behavior of the class or its instance. @[subsection] Member Data and Method @[subsection] Class supports three types data of fields: @[list] --@[code]constant@[code]: declared with keyword @[code]const@[code]; --@[code]static variable@[code]: declared with keyword @[code]static@[code]; --@[code]instance variable@[code]: declared with keyword @[code]var@[code]; --@[code]instance invariable@[code]: declared with keyword @[code]invar@[code]; @[list] Such fields can be declared with or without explicit types, and with or without default or initialization values, in the same way as specifying types and/or default values for function parameters. For example, the following can be used for instance variables, @[code] var variable; var variable = init_value; var variable : typename; var variable : typename = init_value; @[code] Dao class supports multiple type of member methods such as constructor, static method, instance method and methods for overloaded operators. Class methods must be declared with keyword @[code]routine@[code] for constructors and normal methods, or keyword @[code]operator@[code] for operator overloading. Example, @[code] class Klass { var value = 123 routine AddValue( val: int ){ value += val } } @[code] The access of class fields and methods can be restricted by three permission keywords: @[list] --@[code]public@[code]: publically accessible without restriction; --@[code]protected@[code]: accessible from the class and its derived classes; --@[code]private@[code]: only accessible from the class; @[list] @[subsection] Class Instance @[subsection] The following is a simple class without a constructor: @[code] class Contact { const description = 'This is a class for contact information' var name = 'Nobody' var address = 'Nowhere' routine Show(){ io.writeln( name, 'lives at', address ) } } @[code] There are two ways to create instances of such simple classes: @[list] -- Call the default constructor which will initialize the instance variables with their default values: @[code] obj = Contact() @[code] -- Enumerate the values of the instance variables: @[code] obj = Contact.{ 'Mike', '123 Main Street' } @[code] Field names can be specified for the values, so that these values can appear in arbitrary order: @[code] obj = Contact.{ address = '123 Main Street', name = 'Mike' } @[code] Please note, only simple classes without base classes and explicit constructors can use this kind of instance construction. @[list] @[subsection] Class Constructor @[subsection] In a class, the methods with the same name as the class itself are considerred as class constructors. When the class name is used for a function call, a class instance will be created and then the appropriate constructor will be invoked to initialize the instance. Here is the same class as above, but with a constructor: @[code] class Contact { const description = 'This is a class for contact information' var name = 'Nobody' var address = 'Nowhere' routine Contact( name : string, address = 'Unknown' ){ self.name = name self.address = address } routine Show(){ io.writeln( name, 'lives at', address ) } } @[code] Now calling the constructor is the only way to create an instance of @[code]Contact@[code]: @[code] var obj = Contact( 'Mike', '123 Main Street' ) @[code] Class constructors can be overloaded in the same way as normal functions: @[code] class Contact { const description = 'This is a class for contact information' var name = 'Nobody' var address = 'Nowhere' routine Contact( name : string, address = 'Unknown' ){ self.name = name self.address = address } routine Contact( another : Contact ){ name = another.name address = another.address } routine Show(){ io.writeln( name, 'lives at', address ) } } @[code] @[subsection] Static Method @[subsection] Static methods can be defined by specifying the @[code]static@[code] keyword before the @[code]routine@[code] keyword. Such method can be invoked without class instance, for this, they are not allowed to use class instance variables. @[code] class Klass { var id = 123 static info = 'Klass' static routine StaticMethod(){ id = 1 } # Wrong; static routine StaticMethod(){ io.writeln( info ) } # Correct; } var obj = Klass::StaticMethod() @[code] @[text] @[name] dao.class.inheritance @[name] @[title] Inheritance @[title] @[text] In Dao, new classes can be derived from existing classes (or C data types) to inherit their functionalities and to establish is-a relationship between objects. When a class A is derived from another class B, class A is usually referred to as a child, or derived, or sub- class, and the class B is ussually referred to as a parent or base class. To derive one class from another, the base class must be placed after the class name of the derived one separated by a colon. Here is a simple example, @[code] class Base { var id = 0 routine Base( i = 0 ){ id = i } } class Sub : Base { var value = 'abc' } var obj = Sub() @[code] In this example, an instance of the derived class is created by calling its implicit default constructor, which will call an constructor (either implicit or explicit) of the derived class that accepts no parameters. When defining a constructor of a derived class, the constructor of a base class can be called explicitly, by placing the call right after the constructor signature of the derived on separated by a colon. The call to a base constructor can only accept the parameters (or the @[code]self@[code] variable) of the constructor of the derived class. @[code] class Sub : Base { var value = 'abc' routine Sub( v = 'ABC', i = 3 ) : Base( i ) { value = v } } @[code] @[code] load meta class Base { var id = 0 routine VirtMeth(){ io.writeln( 'Base::VirtMeth()' ) } routine NormMeth(){ meta.self( self ).VirtMeth() # emulate virtual method call; } } class Sub : Base { var value = 'abc' routine VirtMeth(){ io.writeln( 'Sub::VirtMeth()' ) } } var o : Base = Sub() o.NormMeth() # prints: Sub::VirtMeth() @[code] @[text] @[name] dao.class.operator @[name] @[title] Operator Overloading @[title] @[text] Class instances can be used in operations in a way similar to those of built-in types, if the classes define methods to overload relevant operators. Basic arithmetic operators are also supported for overloading. For example, addition operator can be supported for a class by defining a @[green]operator+@[green] method in the following class, @[code] class Integer { var value = 0 routine Integer( val = 0 ){ value = val } static operator +( A : Integer, B : Integer ){ io.writeln( 'Integer + Integer' ); return Integer( A.value + B.value ); } } var I1 = Integer( 123 ) var I2 = Integer( 456 ) var I3 = I1 + I2 @[code] Operation for accessing member fields can also be redefined as overloaded operator @[green]operator .field_name@[green] and @[green]operator .field_name=@[green]: @[code] class Integer { private var value = 0 public routine Integer( val = 0 ){ value = val } operator .value(){ io.writeln( 'get value' ) } operator .value=( v : int ){ value = v; io.writeln( 'get value' ) } } var I = Integer( 123 ) var v = I.value I.value = 456 @[code] Some classes may behavior like containers and have members that can be accessed using an index or a key. To support member accessing using an index or key, such class can overload operator @[green][]@[green] and @[green][]=@[green] @[code] class IntList { var ints = {} routine Append( value : int ){ ints.append( value ) } operator []( index : int ){ return ints[index] } operator []=( value :int, index : int ){ ints[index] = value } } var ilist = IntList() ilist.Append( 123 ) ilist.Append( 456 ) ilist[0] = 789 io.writeln( ilist[0] ) @[code] TODO: cast @[text] #{ In order to avoid creating new class instance whenever such addition operation is performed, one can define another addition method that can be used when the instance created by this operation is a temporary object and can be reused. This method will take the reusable temporary object as the first parameter, @[code] class Integer { var value = 0 routine Integer( val = 0 ){ value = val } static operator +( A : Integer, B : Integer ){ io.writeln( 'Integer + Integer' ); return Integer( A.value + B.value ); } static operator +( C : Integer, A : Integer, B : Integer ){ io.writeln( 'Integer = Integer + Integer' ); C.value = A.value + B.value; return C; } } var I1 = Integer( 123 ) var I2 = Integer( 456 ) for(var i = 1 : 3 ) I3 = I1 + I2 @[code] #} @[name] dao.class.mixin @[name] @[title] Mixin Class @[title] @[text] Definition of new classes may also include other classes that will be used as components and merged (mixed) into the new classes. The component (mixin base) classes can be specified in a pair of brackets following the class name. Only classes without parent classes can be used as mixin bases. @[code] class Base { var value = 456 routine Meth2(){ io.writeln( self, value ) } } class Mixin ( Base ) { var index = 123 routine Meth(){ io.writeln( self, index, value ) } routine Meth2( a : string ){ io.writeln( self, index, value, a ) } } # # The "Base" class will be presented only once in "Klass": # class Klass ( Base, Mixin ) { var index = 123456 routine Meth2( a : int ){ io.writeln( self, index, value, a ) } } var k = Klass() io.writeln( k.index ) k.Meth() k.Meth2() k.Meth2( 'abc' ) k.Meth2( 789 ) @[code] @[text] @[name] dao.interface @[name] @[title] Interfaces @[title] @[text] Dao supports two types of interfaces: Abstract Interface and Concrete Interface. Abstract interface only defines a set of method signatures that a type must support in order to be compatible with the interface type. Concrete interface, on the other hand, implement a set of methods defined by an abstract interface for a target type. @[text] @[name] dao.interface.abstract @[name] @[title] Abstract Interface @[title] @[text] Abstract interface is a type that describes how an object can be used, by specifying what methods and overloaded operators the object can support. An object is compatible (matching) to an interface type, if the object supports all the methods and operators that are specified by the interface. Interface is an abstract type, since no instance can be created from an interface, also all the methods of an interface are abstract without implementation. Here is a simple interface that contains a size checking method, @[code] interface HasSize { routine size()=>int } @[code] Now we can define a function that can take a parameter of any object that is compatible to this interface, @[code] routine PrintSize( object: HasSize ) { io.writeln( object.size() ) } @[code] Then this function can be called upon types such as @[code]string@[code], @[code]list@[code] or @[code]map@[code] etc. @[code] PrintSize( 'hello world' ) PrintSize( { 1, 2, 3 } ); @[code] Interface supports inheritance in the same way as class does, @[code] interface Resizable : HasSize { routine resize( size :int ) } @[code] Similarly, @[code] routine Resize( object: Resizable, size: int ) { io.writeln( 'old size:', object.size() ) io.writeln( 'new size:', size ) object.resize( size ) } var ls = {} Resize( ls, 5 ) io.writeln( ls ) @[code] Interface also supports operator overloading, however, built-in operators for built-in types cannot be checked against an interface, because they are not implemented as methods. So interfaces are normally more useful with class instances and wrapped C/C++ types. Interfaces with the same set of abstract methods are interchangeable, @[code] interface HasSize2 { routine size()=>int } routine PrintSize2( object: HasSize2 ) { o :HasSize = object; # assign an object of "HasSize2" to a variable of "HasSize"; io.writeln( object.size() ) } PrintSize2( {} ); @[code] @[text] @[name] dao.interface.concrete @[name] @[title] Concrete Interface @[title] @[text] When a type need to be used in places where an abstract interface type is expected, but the type does not support all the methods defined by the abstract interface, a concrete interface can be defined for the (target) type to implement the methods of the abstract interface, so that the target type can be used wherever the abstract interface type is expected. For example, @[code] # Abstract interface: interface SomeInterface { routine SomeMethod(); } # Concrete interface: interface SomeInterface for SomeTargetType { routine SomeMethod(){ } } @[code] Here the second interface type is a concrete one. It has a generic-like type name: @[code]interface>@[code], which stands for the concrete interface type itself. When a value of the target type @[code]SomeTargetType@[code] is used in places where the interface type @[code]SomeInterface@[code] is expected, the value will be automatically casted to the interface type by wrapping it as an instance of the concrete interface. Such instance also has a generic-like type name: @[code]SomeInterface@[code]. And it is also automatically casted to the target type when needed. Concrete interfaces also support inheritance: @[code] interface SubInterface : SomeInterface { routine SomeMethod2(); } interface SubInterface for SomeTargetType : SomeInterface { routine SomeMethod2(){ } } @[code] The abstract interface @[code]SubInterface@[code] must have been derived from @[code]SomeInterface@[code]. And the target types must be the same. More example, @[code] interface InterA { routine size()=>int routine serialize()=>string } interface InterB : InterA { routine []( index: int ) => float } interface InterA for int { routine size(){ io.writeln( "InterA::size()", std.about(self) ) return self + 1; } routine serialize() => string { return "abc" + (string) self; } } var a: InterA = 123 # a: value type InterA; variable type InterA; var b = (InterA) 123; # b: value type InterA; variable type InterA; var c: InterA = 123 # c: value type InterA; variable type InterA; io.writeln( a.size() ) io.writeln( (int) a ) var d: int = c var e: list> = { 123 } e.append(456) var f: list = { 678 } f.append(456) io.writeln( e, f ) @[code] @[text]