lyng/docs/OOP.md

5.7 KiB

OO implementation in Lyng

Declaration

The class clause looks like

class Point(x,y)
assert( Point is Class ) 
>>> void

It creates new Class with two fields. Here is the more practical sample:

class Point(x,y) {
    fun length() { sqrt(x*x + y*y) } 
}

val p = Point(3,4)
assert(p is Point)
assertEquals(5, p.length())

// we can access the fields:
assert( p.x == 3 )
assert( p.y == 4 )

// we can assign new values to fields:
p.x = 1
p.y = 1
assertEquals(sqrt(2), p.length())
>>> void

Let's see in details. The statement class Point(x,y) creates a class, with two field, which are mutable and publicly visible.(x,y) here is the [argument list], same as when defining a function. All together creates a class with a constructor that requires two parameters for fields. So when creating it with Point(10, 20) we say calling Point constructor with these parameters.

Form now on Point is a class, it's type is Class, and we can create instances with it as in the example above.

Class point has a method, or a member function length() that uses its fields x and y to calculate the magnitude. Length is called

default values in constructor

Constructor arguments are the same as function arguments except visibility statements discussed later, there could be default values, ellipsis, etc.

class Point(x=0,y=0) 
val p = Point()
assert( p.x == 0 && p.y == 0 )
>>> void

Methods

Functions defined inside a class body are methods, and unless declared private are available to be called from outside the class:

class Point(x,y) {
    // public method declaration:
    fun length() { sqrt(d2()) }

    // private method:
    private fun d2() {x*x + y*y}
}
val p = Point(3,4)
// private called from inside public: OK
assertEquals( 5, p.length() )
// but us not available directly
assertThrows() { p.d2() }
void
>>> void

fields and visibility

It is possible to add non-constructor fields:

class Point(x,y) {
    fun length() { sqrt(x*x + y*y) } 

    // set at construction time:   
    val initialLength = length()
}
val p = Point(3,4)
p.x = 3
p.y = 0
assertEquals( 3, p.length() )
// but initial length could not be changed after as declard val:
assert( p.initialLength == 5 )
>>> void

Mutable fields

Are declared with var

class Point(x,y) {
    var isSpecial = false
}
val p = Point(0,0)
assert( p.isSpecial == false )

p.isSpecial = true
assert( p.isSpecial == true )
>>> void

Private fields

Private fields are visible only inside the class instance:

class SecretCounter {
    private var count = 0
    
    fun increment() {
        count++
        void // hide counter
    }
    
    fun isEnough() {
        count > 10
    }
}
val c = SecretCounter()
assert( c.isEnough() == false )
assert( c.increment() == void )
for( i in 0..10 ) c.increment()
assert( c.isEnough() )

// but the count is not available outside:
assertThrows() { c.count }
void
>>> void

It is possible to provide private constructor parameters so they can be set at construction but not available outside the class:

class SecretCounter(private var count = 0) {
    // ...
}
val c = SecretCounter(10)
assertThrows() { c.count }
void
>>> void

Theory

Basic principles:

  • Everything is an instance of some class
  • Every class except Obj has at least one parent
  • Obj has no parents and is the root of the hierarchy
  • instance has member fields and member functions
  • Every class has hclass members and class functions, or companion ones, are these of the base class.
  • every class has type which is an instances of ObjClass
  • ObjClass sole parent is Obj
  • ObjClass contains code for instance methods, class fields, hierarchy information.
  • Class information is also scoped.
  • We acoid imported classes duplication using packages and import caching, so the same imported module is the same object in all its classes.

Instances

Result of executing of any expression or statement in the Lyng is the object that inherits Obj, but is not Obj. For example it could be Int, void, null, real, string, bool, etc.

This means whatever expression returns or the variable holds, is the first-class object, no differenes. For example:

1.67.roundToInt()
1>>> 2

Here, instance method of the real object, created from literal 1.67 is called.

Instance class

Everything can be classified, and classes could be tested for equivalence:

3.14::class
1>>> Real

Class is the object, naturally, with class:

3.14::class::class
1>>> Class

Classes can be compared:

assert(1.21::class == Math.PI::class)
assert(3.14::class != 1::class)
assert(π::class == Real)
π::class
>>> Real

Note Real class: it is global variable for Real class; there are such class instances for all built-in types:

assert("Hello"::class == String)
assert(1970::class == Int)
assert(true::class == Bool)
assert('$'::class == Char)
>>> void

More complex is singleton classes, because you don't need to compare their class instances and generally don't need them at all, these are normally just Obj:

null::class
>>> Obj

At this time, Obj can't be accessed as a class.

Methods in-depth

Regular methods are called on instances as usual instance.method(). The method resolution order is

  1. this instance methods;
  2. parents method: no guarantee but we enumerate parents in order of appearance;
  3. possible extension methods (scoped)

TBD

argument list