Every value has a type, which is a collection of properties and methods aggregated (via inheritance) from one or more objects, classes or mixins:

Objects, classes, and mixins are mutable, first-class values. Object, Class, Mixin and various collection types are metaclasses, used to create and define the properties and methods for a new type. Like other collections, the property and method content of an object, class or mixin is typically built using a 'this' block.

Objects

Objects offer the simplest structure for inheritable properties and methods. This simple structure makes them very convenient to use. Despite their structural simplicity, objects are quite versatile.

Creating a new Object

Let's start by creating a basic "singleton", an object which has its own distinct state (properties) and behavior (methods). To build a new object, use +Object with a 'this' block. In the block, specify the object's named properties (using ':') and methods (using ':='):

# Create a new object
thing = +Object
	# Specify and initialize its properties
	position: +Xyz(0.0, 0.0, 0.0)
	velocity: +Xyz(1.0, 0.0, 0.0)
	
	# Define method that calculates new position after dt seconds
	Move:= [dt]
		.position = .position + .velocity * dt

Having now created 'thing' and described its two properties and method, we may now call its defined .Move method on itself, thereby altering its position:

thing.Move(0.1)  # calculates new .position to be (0.1, 0.0, 0.0)

Properties and Methods

An earlier chapter described how to use a value's properties and methods. Here we talk about their names and content.

Property and Method Names

An object has a single namespace for uniquely naming each of its properties and methods. These helpful (but not enforced) conventions apply to property and method names:

These distinctions can have practical consequences. One might want to use 'each' to iterate over a type to see only the current state of its properties. One might want to 'freeze' a type, so that only its properties can be altered, but not its methods (even those that underly computed properties).

Specifying the Value of an Object's Property or Method

An object has only one namespace. Any name in any object can hold any type of value: null, true, a number, string, symbol, collection, method, closure, yielder, class, etc. As a helpful convention, we often label a certain object's name a method when it stores any kind of method, closure or yielder, and a property otherwise.

Part of the reason we do this is because this distinction matters to the '.' and ':' operators, which access a name's stored value when it is a property, but call it when it is some kind of method. Because of this behavior:

Computed Properties

Computed properties are part-property and part-method. They look and behave like simple properties, However, under the covers they make use of a method or get/set closure to retrieve or change its value. The following example defines 'radians' as a normal property and 'degrees' as a computed property:

angle = +Object
	radians: 0.
	degrees:= +[]
		[] {.radians * 180. / Float.Pi}
		[a] {.radians = a * Float.Pi / 180.}

Notice that we used the ':=' operator for 'degrees', since we are storing a get/set closure into it. However, if we later want to assign its property value to some number (in degrees), we would then use ':' or '.', which will automatically make use of this get/set closure to retrieve or alter its value.

The get/set closure used by the 'degrees' computed property links the computed value accessible via 'degrees' to the float value held in 'radians'. As a result, angle's single value can now be accessed in either radians or degrees:

# Set the angle using degrees...
angle.degrees = 45.
# ... retrieve it in radians
angle.radians # 0.785398

Computed properties are useful and convenient in many situations:

Several properties in the core and World type libraries (e.g., the 'size' property for collections) are implemented using computed properties.

Closure and Yielder Iterators

An object may define closure or yielder iterators as named methods, as illustrated by the Each method provided for collections and Range. With an iterator as an object method, we want it to be re-usable, which stateful iterators are not. To address this, define a named iterator method as an factory that returns a new iterator each time it is called.

Yielder methods already work this way:

mathlib = +Object
	Fibonacci:= *[start=1, inc=0]
		while true
			yield true,start
			start,inc = start+inc, start
			
# using mathlib.s Fibonacci iterator factory
each n in mathlib.Fibonacci
	...

To make a closure factory, define a method that returns a newly created closure whose closure state is built using parameters passed to the factory method:

mathlib
	Odds:= [start=1]
		+[start] []
			local s,start = start, start+1
			s, s*2-1

each n in mathlib.Odds
	...

Object Prototypes

Objects are valuable for more than singletons; they are commonly used to create several objects that share common behavior and properties. To do this, begin by creating a prototype object that describes the methods, constants, default properties, and "static" properties that those objects share in common. A prototype is created exactly the same way as a singleton:

# Define a prototype called 'Ball'
Ball = +Object
	# Specify the properties for a ball
	position: +Xyz(0.0, 0.0, 0.0)
	velocity: +Xyz(0.0, 0.0, 0.0)
	
	# Define method that calculates new position after dt seconds
	Move:= [dt]
		.position = .position + .velocity * dt

We can now use this defined prototype to create two new balls. Notice we use +Ball for this, instead of +Object:

fastball = +Ball
	position: +Xyz(0.0, 0.0, 0.0)
	velocity: +Xyz(100.0, 0.0, 0.0)

balloon = +Ball
	position: +Xyz(0.0, 50.0, 0.0)
	velocity: +Xyz(0.0, 10.0, 0.0)

balloon and fastball are initialized with their own property values, which represents the distinct state for each ball. Since they were created by Ball, balloon and fastball automatically inherit its Move method. So:

fastball.Move(0.1) # position is now +Xyz(10.0, 0.0, 0.0)
balloon.Move(0.1)  # position is now +Xyz(0.0, 51.0, 0.0)

Prototypical Inheritance

The way that objects inherit from their prototype is very straightforward. If you try to use an object's named property or method:

It follows the ancestry back up the creation chain (in reverse order) until it gets to Object, which is the end of the line. This is how balloon was able to use .Move (found in Ball). It also explains how we were able to create a new Ball. +Ball means Ball.New, which works because Ball inherits the .New method from Object.

It is a very simple mechanism. All prototypes are objects and any object may become a prototype for a new object it creates. This means we could, if we want, use +balloon to create a new balloon called redballoon.

With prototypical inheritance, an object automatically inherits every property, computed property and method from every prototype all the way up its inheritance chain. An object may add its own properties and methods or override those it inherits.

Let's explore the design implications for inheritable properties and methods.

Property Initialization and Inheritance

In general, properties should be given values on the object they apply to. Most of the time, this is the lowest level object (rather than specify them on any prototype). However, there are times when it makes sense to define properties on a prototype:

An object can easily override the default value it inherits from its prototype by assigning it a new value at the object level (as the balloon example demonstrates). Assignments always act at the object level, so doing so leaves the prototype's default value unchanged. Although an object cannot explicitly "delete" an inherited property assigning the object's property to 'null' accomplishes the same thing. From a property use perspective, there is no difference between a property that does not exist and one that exists, but is assigned to null. The difference is only noticeable during property reflection.

It is important to establish how a newly created object's properties are initialized. Several distinct approaches are possible:

Method Definition and Inheritance

In contrast to properties, methods are more typically populated at the prototype level. To be reusable by any object, prototype methods should typically be stateless. For this reason, a prototype that wants to offer (stateful) closures or yielders as methods will do so using closure or iterator factories. As with properties, there may be situations where it makes sense for a prototype to use a stateful method, particularly when it applies across multiple objects (rather than to one specifically).

On the flip side, a key benefit of prototypical inheritance is that it is very easy for any object to define its own specific behavior. This can be very useful for games and other models that make use of one-of-a-kind entities (such as a boss monster). Non-prototype objects may freely use stateful methods (closures and yielders) along with its stateful properties.

In this example, superball is given the Stop capability that no other ball has.

superball = +Ball
	Stop:= []
		velocity: +Xyz(0.,0.,0.)

As with properties, an newly created object's methods can be defined within a 'this' block or as part of an object's constructor (e.g., .New).

Method Overriding

A prototype (or singleton) may specify a new method that replaces an existing same-named method it inherits from a parent prototype. This is called overriding. If the new method wants to make use of the inherited method as part of its logic, it must either:

Let's demonstrate the latter approach by showing a very common scenario: defining a prototype that has its own .New constructor (which must override and make use of the .New method it inherits):

Mover = +Object
	# Override .New with a closure which binds 
	# 'super' to the prior (inherited) .New method (Object.New)
	New:= +[super=.:New] [pos=0.0, velocity=0.0]
		# Use super to create and return a new object
		super()  # equivalent to:  self.(Object.:New)()
			# Finish initialization of new object using parameter info
			pos: pos
			velocity: velocity
			accel: 0.0

# Create a new Mover
obj1 = +Mover(12., 10.)

A similar technique may also be applied to computed properties.

This technique may even be used more than once on the same name within a prototype, much like the use of decorators in other languages (e.g., Python or Javascript). This approach enables a core capability to be wrapped by parasitical pre- or post-logic that might log, validate, handle subscription notifications, etc.

Classes

Classes offer a similar capability to prototypes: they are used to create values that share similar behavior and state structure. Their key advantage over prototypes is that a class-created "instance" value need not have the same hashed-key collection structure nor share the same namespace as its creator. This is why most of Acorn's core types (e.g., List, Text, and Float) are implemented using Classes.

Every class defines two namespaces:

This dual structure makes classes somewhat more complex to define and use than prototypes (which combine both roles into a single namespace). Also note that class instances cannot be singletons.

Other than these differences, the rest of what has been said about objects/prototypes applies to classes.

Class Creation

Rectangle = +Class
	New: +[super=.:New] [len, wid]
		super()
			length: len
			width: wid
 
	.traits
		area: []
			::length * ::width
 
# Instantiate the class using the new operator
rect = +Rectangle(2,6)
rect.area   # 12

Class Creation via Composition

Like subclassing, composition takes advantage of a base class's methods. However, rather than inheriting methods from the base class, it simply makes use of them. In effect, it is a wrapper around a generic collection type, presenting a new interface for values of its type and hiding the base class's methods from view.

This example creates a Complex number class that is wrapped around a two-value List:

Complex = +Class
	New:= [r,i]
		self.(List.:New)(r,i) if r.float? and i.float?
	traits = +Mixin
		r := self.(List.traits.:'[]')(0)
		i := self.(List.traits.:'[]')(1)
		'+' := [v]
			+Complex(self.r+v.r, self.i+v.i)

Using +Class and +Mixin ensures Complex builds from two clean namespaces for its methods. The methods defined for Complex use List's methods to store and access the real and imaginary components for a complex number:

+Complex(0.5, 1.3) + +Complex(1.2, 0.2)  # +Complex(1.7, 1.5)

The base class used for the value returned by .New must be created by a subclassable type, such as List, Index or Text.

Class Creation via Sub-classing

In Acorn, a new class is typically defined by subclassing an existing collection class, such as List, Index, Text or Mixin. The new class (and its traits) inherits properties and methods from the specified base class.

This simple example defines a new Set class that subclasses Index:

Set = Index.Subclass
	# Set's class methods & properties go here
	# Set inherits Index.New
	.traits
		# a set's inherited methods go here
		Add:= [mbr]
			self[mbr] = true
		Find:= [mbr]
			self[mbr]

Notice the use of the .Subclass constructor method on Index. It handles the creation of Set's new class and the traits collections and sets up them up to inherit from Index and Index.traits respectively.

With Set defined, we can now use it:

suits = +Set
	.Add('Hearts')
	.Add('Clubs')
suits.Find('Clubs')     # true
suits.Find('Diamonds')  # null

Not all classes support subclassing. In particular, it is not possible to subclass the core library's atomic types, such as Null, Bool, Integer, Float, and Method. It is also not possible to subclass Object.

Class Creation using the C-API

The Acorn C-API makes it possible to define and make use of new classes and methods that are coded in C. This is very useful for defining high-performance classes that manipulate specialized data structures or provide access to system capabilities via external libraries (e.g., OpenGL or database access).

Mixins

Prototypical and classical inheritance are single-inheritance mechanisms by default. An object inherits through a single line of ancestry: its prototype (or class), its prototype's prototype, etc. Most of the time this is sufficient. However, single inheritance can be needlessly inflexible when we want two objects from radically different prototypes/classes to share common behavior. To address the inflexibility of single-inheritance, some professionals favor composition over inheritance, a design approach that many game developers implement using an Entity-Component System.

Another way to overcome the inflexibility of single-inheritance is multiple-inheritance, which Acorn supports via the use of mixins. A mixin, like an object, is simply a hashed-key collection of methods (and sometimes properties). However, unlike an object, a mixin does not create new values, inherits nothing from Mixin (its creator), and makes no use of its own methods. A mixin's sole purpose is to make additional methods inheritable by certain values.

This example defines a trivial Mixin named Square:

Square = +Mixin
	Square:= []
		self * self

Using the .Mixin method makes a mixin's methods inheritable by an object, prototype, class, or mixin. For example, let's add the Square mixin to Integer and Float and then use it:

Integer.traits
	.Mixin Square
3.Square  # 9

Float.traits
	.Mixin Square
4.1.Square  #16.81

As this example demonstrates, a mixin may access methods (such as '*') or properties that are not defined by the mixin, but which can be reasonably expected to be implemented somewhere else in the value's inheritance chain.

Multiple Inheritance Search Order

How does Acorn handle the multiple inheritance "diamond problem", wherein an object inherits from two mixins that use the same name for different methods? It uses the one defined by the more recently added mixin. In essence, the last one specified is the first one checked.

For example, imagine an object that makes use of two mixins:

beachball = +Ball
	.Mixin DiamondSkin
	.Mixin Bouncer

When we call beachball.Throw, Acorn will look for .Throw first in the Bouncer mixin, and use that one if found. If not found in Bouncer, it looks in DiamondSkin. Failing that, it looks for Throw in Ball (the prototype).

Although multiple inheritance offers great flexibility, it carries both a performance and complexity cost when overused. The more parents in the inheritance tree, the more places that have to be checked for any value not found (which returns 'null'). Complex multiple inheritance trees are possible. Acorn will faithfully search them in a disciplined depth-first order, but they can become too complex for other people to easily follow.

Mixins and State

Since, mixins are, for the most part, injected into prototypes and classes, they should contain just stateless methods (and computed properties) and not instance-specific, mutable state. If a mixin's methods provide an orthogonal extension which makes use of properties (state) not defined by the primary inheritance chain, how do those properties get initialized for any new instance that uses the mixin?

There are three ways to initialize the properties referenced by a mixin:

Type-checking a Value

Determining the type for any value is deceptively easy. Just use the .type property on it:

# The type of an object is its prototype
+Object.type  # === Object

# The type of an instance is its class's traits
1.type        # === Integer.traits

# The type of an object with injected mixin is a List
m = +Object
	.Mixin Amixin
m.type        # == +List(Amixin, Object)

As these examples demonstrate, an unfortunate side-effect of Acorn's inheritance flexibility is that what you get back as the type for a value varies depending on whether that type uses prototypical, classical or multiple inheritance. And, when mixins are injected, they are structurally captured using ordered Lists.

Fortunately, most of the time we are not really interested in what the value's type is. We just want to know whether the value implements a specific method or makes use of a certain type's methods. Acorn's duck typing makes this possible.

The .uses? method (implemented by All) may be used to check if a value implements a specific property name. This provides valuable information about we can do with a value. For example:

val.uses?('<=>')     # true if the value is comparable
val.uses?('[]')      # true if the value is indexable
val.uses?('Each')    # true if the value's members can be iterated

Determining whether a value inherits from a certain type is accomplished using pattern matching:

+Object ~~ Object   # true
1 ~~ Integer        # true
m ~~ Amixin         # true
m ~~ Object         # true

Additionally, some classes provide specific methods for this purpose:

1.integer?          # true

Metaclasses and Reflection

Thus far, this chapter has introduced the most commonly used Type features in Acorn: how to define prototypes, classes and mixins and then use them to create new values that properly inherit the appropriate methods and properties. Let us turn to other type capabilities offered by Acorn: altering a type's definition dynamically at any time and interrogating the properties or methods of by a type (reflection).

All these capabilities are possible because objects, classes and mixins are mutable first-class values. Being values, they inherit the method capabilities provided by their metaclasses: Object, Class and Mixin.

Mutable Classes

Since objects, classes and mixins are just mutable values, their properties and methods may be dynamically altered at any time. Even the built-in classes provided by the core type library may be changed.

For example, the following code adds the Square method to Integer, thereby making it available to all integers:

Integer.traits
	Square:= []
		self * self
		
3.Square   # 9

Notice that we added the new method to Integer.traits, rather than Integer, in order to make the method work properly on instances created by Integer.

Type Reflection

How Inheritance Works

Inheritance is controlled by three properties:

Acorn is able to efficiently support many inheritance techniques by having the different varieties of types use these properties differently:

The inheritance search starts with self, if .inheritype is true. Failing that, it proceeds depth-first through the parent tree starting with .type. When it encounters a list of types, it looks into each in order. When it encounters null, it backs up. If the inheritance search fails to find a match for the desired property, the "All" type is checked last. If it fails too, 'null' is returned.

Composition vs. Inheritance

Although this chapter focused entirely on dynamic, multi-level, multi-parent inheritance, Acorn supports just as well a design where inheritance has been flattened to one level, accomplished by statically copying in the inheritable properties down into every type that uses them. Such as strategy might improve performance. However, it would be problematic if the world is in the habit of dynamically modifying its types after it is loaded up and running.

_