Class Objects

The class-objects system is a trivial implementation of modules, mixins, and classes, included in the prelude.

Classes defined with this system are essentially alternative syntax for creating objects and their methods. Thus, you're always just dealing with objects, and to the outside world there is no real distinction between those defined with this system and those not.

Defining a module (see module:) registers its body associated with its name into the Macro Environment. An include: dispatch expands that module's body "onto" its target.

Class object and module bodies used with these macros are very similar - indeed, they are evaluated with the exact same translations, just in different contexts. A class definition's body is evaluated upon class creation, to set up the meta-object. A module's body is evaluated wherever it is include:d.

Inside of these definition bodies, := has a certain context implied for the right hand side. For class objects, it's the object receiving the message, and for modules it's the target being included onto. This context can be referred to explicitly as me, which is similar to self or this in other languages (but it's not a keyword in Atomo).

In addition to this rule, methods named new or beginning with new. are assumed to be initializers. Their right-hand side should be a block containing expressions to evaluate on the new instance.

Defining Classes

class: body &extends: Object  Object
  | body is-a?: Block

Creates an anonymous class object with a given body, cloning a specified parent, or Object by default.

Example:

> X = class: { new := { foo = 1 } }
<object (delegates to 1 object)>
  (me: <this>) new := o:1282 clone do: { foo = 1 }
> X new foo
1
> Y = class: { new := { foo = 1 } } &extends: 1
<object (delegates to 1 object)>
  (me: <this>) new := o:1287 clone do: { foo = 1 }
> Y is-a?: 1
True
> Y new + 2
3
class: x  Object
  | x is-a?: Dispatch

Creates a class object, specifying a name along with the body. The full form is class: A: { ... }; hence x is a keyword dispatch, with the message name being the name of the class.

If the class does not exist, it is defined as the given name. If it does exist, the classes body is evaluated on it, behaving very similarly to the "reopening" semantics found in Ruby.

Class objects defined with this method get a pretty definiton for free, which pretty-prints as the classes name. This can be overridden in the class body.

Example:

> class: X: { new := { foo = 1 } }
X
> X new
X
> X new foo
1
me  any

When running a method, this is bound to the to the current object instance or. In the class body, this refers to the meta-object.

Modules & Mixins

module: x  @ok
  | x is-a?: Dispatch

Registers a module, specifying the name along with the body. The full form is module: A: { ... }; hence x is a keyword dispatch, with the message name being the name of the module.

This macro just expands to @ok; its sole use is for its side-effects.

Example:

> module: A: { foo = 1 }
@ok

See include: for where things get interesting.

target include: name  any
  | name is-a?: Dispatch

Expand a module's body onto target, finding the module via name.

If the module cannot be found, @unknown-module: is signaled.

Example:

> module: A: { foo = 1 }
@ok
> include: A
1
> foo
10
> include: B
ERROR: <error @(unknown-module: "B")>

When used inside of a module or class object body, it specifically targets the meta-object or module target, respectively. That is, a module can be included into another module or into a class.

Example:

> module: Subtractable: { sub: x := add: (-1 * x) }
@ok
> class: Adder: { include: Subtractable; add: n := @(adding: n) }
Adder
> Adder add: 10
@(adding: 10)
> Adder sub: 10
@(adding: -10)

This mixing-in can also be done on an arbitrary target.

Example:

> module: Subtractable: { sub: x := add: (-1 * x) }
@ok
> class: Adder: { add: n := @(adding: n) }
Adder
> Adder include: Subtractable
@ok
> Adder add: 10
@(adding: 10)
> Adder sub: 10
@(adding: -10)