Object-Like Functions

The Lavender standard library makes heavy use of functions that act like objects. Object-like functions (or simply objects) are typically functions that return a piecewise nested function whose inputs are functions, although the concept of object-like functions is unknown to the language. Syntactically, object-like functions are defined like this.

(def <name>(<fields...>) =>
    (def obj(i)
        => (def(<args...>) => <body>) ; i = \<methodname>\
        => ...
    )
)
The outer function is similar to a constructor, where its parameters are analogous to object fields. The returned function (generally named obj) is the equivalent of the current instance (the this or self reference), and its inner functions are the equivalent of methods. Object-like functions may take no parameters; in this case the definition is that of a singleton object. Singleton object instances are typically named value.

Singleton Objects (Values)

An object that takes no parameters is called a singleton object, or simply a value. Values in the standard library include the empty list Nil and the enpty option None. Both of these values are objects which act like lists and options, respectively. However, values may contain no methods at all and be used only for the unique value they define. Enumeration types may use these kinds of values.

> def Singleton() => def value(i) => \value    ' Define a marker value
global$Singleton:value
> Singleton
global$Singleton:value
> Singleton = Singleton
1.0

Forwarding Functions

Methods on objects are called by passing a special value to the object. However, this requires the use of square bracket notation not only on the object itself, but also on the function value returned by the object. To alleviate this syntactic messiness, the special values passed to the object are typically forwarding functions. Forwarding functions are infix functions that relay the remainder of their arguments to their first argument. The use of forwarding functions enables object methods to be called in infix notation.

> def i_add(obj, param) => [[obj](\add\)](param)      ' Define forwarding function
global:add
> (def NumStore(x) =>                                 ' Simple object that stores a number
>     (def obj(i)
>         => (def(y) => x + y) ; i = \add\
>     )
> )
global:NumStore
> NumStore(2) add 1                                   ' Call function
3.0
Because methods are defined within the objects and not within forwarding functions, virtual methods may be implemented by returning a different method when passed the same forwarding function.

Prototypes, Self, and Polymorphism

Objects may extend other objects through a prototype-based inheritance scheme. Any object may be used as the prototype for another, although an object may not have multiple prototypes. Extending a prototype is done by defining a method for the prototype function which returns the prototype and forwarding to that prototype inside the object.

(def derived(args) =>
    (def obj(i)
        => base(args) ; i = \prototype
        => (...other methods...)
        => [prototype(\obj)](i) ; rest
    )
)
Note that the function value \obj is the (static) current object.

Overriding Methods

Methods in prototype objects may be overridden in derived objects without any special syntax. Simply define a method for the same forwarding function in the derived object to override the method in the prototype object. The override will be found and called by users of the object and any further derived objects. However, the base prototype methods will continue to call the base method.


> def i_getMyString(obj) => [obj](\getMyString\)      ' Forwarding function
global:getMyString
> (def base(x) =>
>     (def obj(i)
>         => str(x) + (\obj getMyString) ; i = \str
>         => " from base" ; i = \getMyString\         ' Method in base
>     )
> )
global:base
> (def derived(x) =>
>     (def obj(i)
>         => base(x) ; i = \prototype
>         => " from derived" ; i = \getMyString\      ' Method in derived
>         => [prototype(\obj)](i) ; rest
>     )
> )
global:derived
> base(1) getMyString                                 ' Calls base method
 from base
> str(base(1))                                        ' Calls base method
1.0 from base
> derived(1) getMyString                              ' Calls derived method
 from derived
> str(derived(1))                                     ' Calls base method (!)
1.0 from base

Polymorphic Prototypes

In the last example, the method defined for str in the base was calling the base method despite the actual object being a derived object. This is because the value of \obj (the current object) refers to the base object from within its definition. In order for methods in base to call methods in derived, the current object must be passed to base when the derived object is created. Such prototypes that accept the current object as a parameter are called polymorphic prototypes, and are similar to interfaces or abstract classes. To modify the above example to use polymorphic prototypes, base should be made into a polymorphic prototype and a convenience object should be defined to allow use of the base object without passing an explicit current object. A polymorphic prototype should use always use its current object parameter to refer to the current object, except when it itself extends a prototype, in which case \obj is used as the argument to the prototype function.


> def i_getMyString(obj) => [obj](\getMyString\)      ' Forwarding function
global:getMyString
> (def _base_prototype(self, x) =>                    ' Base object
>     (def obj(i)
>         => str(x) + (self getMyString) ; i = \str
>         => " from base" ; i = \getMyString\         ' Method in base
>     )
> )
global:_base_prototype
> (def base(x) =>                                     ' Base convenience object
>     (def obj(i)
>         => _base_prototype(\obj, x) ; i = \prototype
>         => [prototype(\obj)](i) ; rest
>     )
> )
global:base
> (def derived(x) =>
>     (def obj(i)
>         => _base_prototype(\obj, x) ; i = \prototype
>         => " from derived" ; i = \getMyString\      ' Method in derived
>         => [prototype(\obj)](i) ; rest
>     )
> )
global:derived
> base(1) getMyString                                 ' Calls base method
 from base
> str(base(1))                                        ' Calls base method
1.0 from base
> derived(1) getMyString                              ' Calls derived method
 from derived
> str(derived(1))                                     ' Calls derived method
1.0 from derived
The convenience object may also be left out, in which case the polymorphic prototype is known as an abstract prototype. By convention, abstract prototypes are not created directly, but are only extended by other objects.