Matte Language: Quick Guide


1. Introduction

This guide is intended for those who are already familiar with programming concepts from popular imperative languages like Python, C/C++, C#, and Java. Matte uses a lot of the same conventions and features, with some key differences.

Throughout the guide, code samples will be provided. If using a javascript-enabled browser, you may hover your pointer / tap the code to get fine details about the syntax used. For example:

    @:myFunction = ::{
        print(:"Hello, world!");
    }
    myFunction();


2. Overview

Matte is a dynamically typed, imperative language with C-like features. You will find many concepts familiar to existing languages that match this profile. For example, C-like single and multiline comments are readily available:
    // A comment.
    /* A multi-line comment. */
As such, every line that isnt a comment is part of a statement. Statements may span multiple lines, but statements may optionally end in a ";" If missing, the compiler will attempt to determine the proper ending of the statement through newline detection.

Remember that every statement is part of some function's scope. There is only one kind of scope: function scope. This is the core of Matte as a language: everything is part of a statement within a function. This includes statements written "nakedly" outside a user-defined function: even here statements are part of a special function. This is referred to as the toplevel or script function. There is no such thing as global scope. Values declared at the toplevel of a script are not visible outside the script without explicit exporting.
You can think of an entire source file in Matte as one large function.

3. Variables

In Matte, all variables MUST be declared before use. Spontaneous use of variables that are not declared will throw a compilation error.

Variables are declared with the @ symbol:

    // Normal variable.
    @myVariable

    // Adding a colon ':' between the @ and the variable name 
    // will make the variable immutable.
    // Immutable values that are modified will fail to compile.
    @:immutableVariable = 42

You can add an inline assignment with the declaration as well:
    @myOtherVariable = 3

    // Okay to use myOtherVariable since it was declared.
    @another = 4 + myOtherVariable

    // Displays 7
    print(:another);
Variable names can contain any non-symbol character (other than _, which is allowed).

3.1 Types

Variables can be any type, but in most cases, they retain the type of its last assignment. There are only a handful of basic types within Matte:


4. Functions

In Matte functions are a type of value. They can accept arguments, but they can also implicitly use variables using lexical "reference capture", also known as lexical closures. Functions are defined with the function constructor "::". It can then be followed by parantheses "()" to declare any number of argument names, then statement scope blocks "{}" to define its behavior.

When called, functions that have arguments have incoming values bound by name. This allows calling functions with arguments in any order. The binding name can be omitted in certain cases, such as when the function only expects one argument (a leading : is still needed), or when a lexically available variable shares the same name as the binding.

    // declare a new variable that is a function. It takes one argument 
    // that can be referred to by "myString"
    @:myFunction = ::(myString) {
        print(:myString);
    }

    // run the function with one argument.
    // Should print Hello, world!
    // Remember: arguments are bound when called, so they must be named.
    myFunction(myString:'Hello, world!');

    // If the function accepts only one argument, there the name 
    // can be ommitted optionally.
    myFunction(:'Hello, world!');




    // If a function doesn't have any arguments, you may 
    // omit the function argument parantheses.
    //
    // When assigning a function, you may also optionally 
    // omit the '='
    @:myOtherFunction :: {
        return '123';
    }

    // Should print 123
    print(:myOtherFunction());


    // For small functions that only return a simple expression,
    // inline function syntax can be used to make it more readable and 
    // concise:
    
    @:sqrt ::(value) <- value**0.5
    
    // should print 4
    print(:sqrt(:16));




Sometimes, it is useful to run a function immediately after creation without assigning it to a variable. These "anonymous function" expressions are useful for more complex behaviors and convenience:

    // The function can be called immediately using the 
    // () like you would on a function reference.
    ::{
        print(:"I ran immediately.");
    }()

    // The () behaves the same as if it 
    // were a typical variable.
    ::(argument) {
        print(:'Hello, ' + argument);
    }(:'World!!')

    
    // Most commonly, it is convenient to 
    // run an entire block with no arguments, like 
    // in the first example. However, excessive () 
    // can make code harder to read in certain 
    // situations. Alternatively, you can use 
    // "dash function syntax" to accomplish the same behavior:
    ::<={
        print(:"I ran immediately, too!");
    }



    // Using anonymous functions in conjunction with 
    // normal ones allows for convenient methods to 
    // store information.
    @:average = ::<={

        @sum   = 0;
        @count = 0;
        
        // This new function returned then becomes 
        // "average". Because it references "sum" 
        // and "count", those variables persist 
        // within the average function.
        return :: (next) {
            sum   += next;
            count += 1;
            print(message:total / count);
        }
    }



    // should print 1
    average(:1);
    // should print 2
    average(:3);



4.1 Type-strict Functions

In Matte it is possible to place type restrictions on functions. This can help produce more quality code, ensuring that incoming variables are of an expected type. When functions place any of these restrictions, the functions are referred to as "type-strict" functions.

Functions can have any argument or its return value be of a specified type. If type is violated, an error is thrown. Matte supports conversion semantics, so if an incoming variable is not of the required type, the incoming variable will attempt to be converted.
    // Specifies that both a and b must be 
    // of type Number.
    @:myAdder::(a => Number, b => Number) {
        return a + b;
    }

    // Ok! Note how the order doesnt matter 
    // as the name binding determines which 
    // argument is which.
    myAdder(b:4, a:10);

    // Throws an error 
    myAdder(a:'50', b:4);


4.2 Additional Function Features

There exist a few other features for functions, grouped together here as they are not as essential as the other ones.

For functions that have an indeterminate number of arguments, a function is able to have variable arguments. Variable arguments (or so-called varargs) are packed into a plain Object for accessing and passing.
    // The prepended * designates the following name (in this case args)
    // as the variable argument. An object is created with keys matching 
    // the named arguments specified.
    @:sumFunction::(*args) {
        @sum = 0;
        foreach(args) ::(key, value) {
            sum += value;
        }
        return sum;
    }

    // In the sumFunction, these are packed into an object 
    // with the given key-value pair.
    // Should return 40
    sumFunction(a:10, another:30);
    
    // In the case of no arguments, an empty object is passed.
    // Should return 0
    sumFunction();

Conversely, a function may be called with a non-static set of arguments. In such a case, a passed object is then accessed for its string keys and binds arguments to the function call for any match found.
    // The argument list can be any Object.
    @:argList = {
        a: 1,
        b: 2,
        c: 4
    }
    
    @:fn1::(a, b) {
        return a+b
    }
    
    // by using specifying the * in front of the 
    // argument, the call will dynamically bind any arguments 
    // that match what is expected.
    // In this case, it will pull a and b, but ignore c since 
    // its not an expected argument name.
    //
    // Should return 3
    fn1(*argList);


For writing native bindings, often it is useful to have a light interface rather than something heavier like the built in class module. For such cases the traditional dynamic binding approach is available.
When functions are called directly from object accesses, a special variable becomes available for functions that expect it. If the function contains the special argument $, the object is assigned to it.
    // Here an object has a function and some data.
    // Note how the apiFunction contains the special 
    // $ argument.
    @:obj = {
        privateData : 42,
        apiFunction ::($, a) {
            return $.privateData + a;
        }
    }
    
    // Because we are accessing the function 
    // from obj, $ gets assigned to obj for the call.
    //
    // Should return 72
    obj.apiFunction(a:30);

5. Flow Control

Matte's flow control is a bit different than other languages. For example, "while" and "switch" do not exist, but instead, there is "forever" and "match" respectively that do similar things.

5.1 when

When writing more algorithm-focused code, it is a common pattern in C-like languages to use an if statement to return from a function early. In Matte, this pattern is accomplished with the when statement:
    // Fibonacci sequence using "when"
    @:fib ::(n){
        when(n <  1) 0;
        when(n <= 2) 1;
        return fib(:n-1) + fib(:n-2);
    }

    print(:fib(:5));


    // When assigning a function, the '=' can be ommitted optionally
    @:hi_low_mid ::(a, b) {
        when(a < b) "lo"
        when(a > b) "hi"
    
        // if no conditionals are true, when 
        // continues on.    
        return "mid";
    }

    // should print lo
    print(:hi_low_mid(a:10, b:40));
    



5.2 match

match is the analog to C-like's "switch / case". Given an expression and a series of possibilities, match will compute and return a new expression, called a conclusion, for the choice that matches a possibility.
    @expression = 1;


    // The following would print "It's one"

    match(expression) {
        // The syntax is (possibility) : conclusion.
        (0) : print(:"It's zero"),
        (1) : print(:"It's one")
    }
    
    
    
    // match returns an expression, so it can be used like so.
    // note that ONLY the conslusion
    print(:match(expression){
        (0)       : '0',
        (1)       : '1',
        // if multiple possibilities lead to the same conclusion, 
        (2, 3, 4) : '2, 3, or 4!',
        default   : "don't know!"
    });
    
    
    // If it is desired to get the same behavior 
    // as C-like language's "if-else", match can be used 
    // in conjunction with anonymous function expressions 
    // to easily create this structure.
    
    @:judgeValue ::(i) {
        match(true) {
            (i > 20): ::<={
                print(:'greater than 20');
            },
    
            (i < 20): ::<={
                print(:'less than 20');
            },
    
            default: ::<={
                print(:'exactly 20');
            }
        }
    }
    
    // should print 'greater than 20'
    judgeValue(:50);


5.3 for

In Matte, for is a statement. It accepts 2 argument expressions denoting the start and end of the loop, then is followed by an function to run in the loop for each iteration. The function receives one value, which is the value of the for loop's iterator as a Number. The argument for the function can be any name of your choosing.
    // Basic for loop, iterates 10 times in a C-style manner using 0-based indices.
    //
    // Should print 
    //
    // 0
    // 1
    // 2
    // 3
    //
    for(0, 4) ::(i){
        print(:i);
    }

    // If the first value is larger than the second, then
    // it will loop backwards by 1.
    // Should print 
    //
    // 4
    // 3
    // 2
    // 1
    for(4, 0) ::(i){
        print(:i);
    }

5.4 foreach

Similar to for, foreach is a statement that iterates over an Object's keys and values. The key and value arguments for the function can be any names of your choosing. The first is always the key, and the second is always the value corresponding to that key.
    @object = ['a', 'b', 'c'];

    // Should print 
    //
    // 0 -> a 
    // 1 -> b 
    // 2 -> c
    //
    foreach(object) ::(key, value){
        print(:'' + key + '->' + value);
    }
Additionally foreach is a special query function that allows for iteration.

    // Should also print 
    //
    // 0 -> a 
    // 1 -> b 
    // 2 -> c
    //
    object->foreach(::(key, value){
        print(:'' + key + '->' + value);
    });

5.5 if

The if expression in Matte can be used to control or avoid computation of another expression. Unlike in other languages, if and else only work with one expression. If you need multiple expressions, have your expression compute to a function or function expression.
    @myVal = false;

    // if computes the proceeding expression if the 
    // expression within parentheses computes to true.

    // should print false
    if (myVal == false) print(:"myVal is false");

    // should not print
    if (myVal == true) print(:"myVal is true");


    // if an "if" statement requires multiple statements, 
    // a new function can be created and immediately run containing the 
    // expressions to be run. However, in most cases, other 
    // structures can be used to make more readable code.


    // should print 42
    if (myVal == false) ::<={
        @val = 4 * 8 + 10;
        print(:val);
    }

    print(:'myVal is ' + (if (myVal) 'true' else 'false'));

Its important to remember that, in Matte, if is an expression; it computes to a value. Specifically, if returns the computed expression. This makes if equivalent to the ternary ?: seen in some C-like languages.

6. Introspection

Often, using strings and numbers requires more than just concatenation and arithmetic. Every value has the potential for introspection through the query operator (->). Here are the different available queries:

    // Should print '42'
    print(: (-42)->abs);

    // Should print '3'
    print(: 180->asRadians->round);


    // Should print 'Hello, world!'
    print(: "%0, %1!"->format(:['Hello', 'World']))


    @str = 'Hello, world!';

    // Should print 'o'
    print(:str->charAt(:4));

    // Should print '13'
    print(:str->length);
    
    // Should print 'world'
    print(:(str->scan(:'Hello, [%]!'))[0]);

6.1 Type Utilities

Along with the query operator, additional built-in functions for base types are available. These utilities are accessible from the built-in Type values:

7. Messages

You may have noticed that with features such as foreach and for are functionally (no pun intended) different from their C-like counterparts. With each running its own function, something like "break" doesn't fundamentally make sense in Matte.

Instead, however, we have messaging. Messaging allows you to abandon a calling context to an earlier context level. This is accomplished using Listen Functions (`{:::}`) and send()

Listen Functions are similar to dash functions in that all inner statements are run immediately. However, it returns either the function's result OR the result of a send() call. The Listen Function acts as a barrier for messages, much like how try / catch mechanisms work in other languages. For example:
 
    // Assume we have a function that returns an Object with numbered 
    // keys, each referring to its own Object with numbered keys 
    // (an array of arrays), each holding a boolean.
    @my2DArray = make100by100();

    
    // The Listen Function syntax runs all statements immediately.
    @result = {:::} {
        for(0, 100)::(y) {
            for(0, 100)::(x) {
                // If a true cell is found, all functions are stopped 
                // up until the nearest listen function, carrying the expression 
                // 'Found!' with it.
                when(my2DArray[x][y] == true) send(:'Found!');
            }
        }
        
        // If send was not called, the listen function just returns the result 
        // if its function.
        return 'Not found...';
    }


Sometimes, its useful to respond to call a special function when any message is received rather than just getting the result of the send. This can be accomplished by adding a handler clause to the listen function. The handler clause is simple an object that can provide functions to call in the case of messages. For normal messages, the property is "onSend", which is called when send is detected. The return value of a send handler is also the value that is returned from the listen function overall, so the handler effectively supervises the entire listen function.
    {:::}{
        for(0, 100)::(y) {
            for(0, 100)::(x) {
                // If a true cell is found, all functions are stopped 
                // up until the nearest listen function, carrying the expression 
                // 'Found!' with it.
                when(my2DArray[x][y] == true) send(:'Found!');
            }
        }
    } : {
        onSend:::(message) {
            print(:'A message was received: ' + message);
        }
    };


7.1 forever

In contexts where a process or algorithm loops until some end condition is reached, a message-based pattern can be employed with the forever statement. This acts as a conceptual analog to the C-like "while", but instead, a function is run forever. Using message passing via send, the loop can safely be broken:
    @counter = 0;
    {:::} {
        forever ::{
            // when send is called, forever() is escaped.
            if(counter == 10) send();

            counter += 1;
            return true;
        }
    }


Messages should be used in controlled instances, as any uncaught message is a fatal error.

7.2 Error Messages

Any function has the potential to throw an error. In Matte, errors are just a special type of message. Outside of what was mentioned already, you can generate your own errors with the built-in error function:
    // Throws an error.
    error();

    // The error function can take an argument of any type, like send()
    error(:'An error occurred');
    error(:{
        message : 'An error occurred!',
        id : 10
    });

Since not handling a message is a fatal error, it is often useful to specify an error catching mechanism. Error catching is similar to catching regular messages with send:
    @errorExample :: {
        {:::} {
            print(:"1");
            print(:"2");
            error(:'Uh oh!');
            print(:"3");
            print(:"4");
        } : {
            onError:::(message) {        
                print(:"The following error was caught: " + message.data);
            }
        }
    }
    
    // should print:
    //
    // 1
    // 2
    // The following error was caught: Uh oh!
    //
    errorExample();
    
    
    
    
    // this can also be used to protect 
    // your code from external functions that 
    // may encounter an unexpected issue.
    //
    // because "mySafeFunction" implements a 
    // catch, errors that propogate from inner functions 
    // will be caught here, preventing further functions 
    // from being interrupted.
    //
    // 
    @dangerousFunction ::{
        print(:'a');
        print(:'b');
        error(:'Uh oh again!');
        print(:'c');
        print(:'d');
    }
    
    @mySafeFunction ::{
        {:::} {    
            print(:1);
            dangerousFunction();
            print(:2);
        } : {
            onError::(message){        
                print(:'Caught external error');
            }
        }
    }

    
    // Should, in total, print:
    //
    // Starting safe operation..
    // 1
    // a 
    // b 
    // Caught external error 
    // Ending safe operation..
    //
    print(:'Starting safe operation..');
    mySafeFunction();
    print(:'Ending safe operation..');



8. Import

The built-in import function can be used to execute external Matte sources. The VM can be customized to respond to import in a variety of ways, but the default behavior is to search for either a built-in module or to load an external Matte source file. When loading your own sources, import will return the computed value of your source. Sources may return a single value; this what import returns. This can be used to implement information hiding and other design patterns for extensible software. In a file called module.mt:
    @privateData = 'Hello, World!';

    @myModule = {
        getData ::{
            return privateData;
        }
    }


    return myModule;
In another file which is run in the VM:
    @module = import(:'module.mt');

    // prints 'Hello, World!'
    print(:module.getData());

Note: importing the same module more than once, by default, will just return the same computed result from the first import. Take care if you are using a custom import implementation.

8.1 Built-in Modules

Matte comes with a small set of pre-compiled modules that can be loaded using the default VM import.

9. Advanced Features

These features are still considered core parts of the language, but they take into account many of the aforementioned features.

9.1 Operator Overloading

Any object has the potential to react with special behavior to a number of operators. By default, using most operators when the object has not specified now to react to them will throw an error. Using the Object.setAttributes built-in function, reaction to these operators can be specified.
    @special = {
        data : ''
    }

    // throws an error
    special << 'a';


    // setAttributes takes the object to modify 
    // and an attributes object, keyed with strings of the 
    // operator to specify overloading with.
    special->setAttributes(:{
        '<<' ::(value) {
            special.data = special.data + value;
        }
    });


    special << 'a';
    special << 'b';

    // prints 'ab'
    print(:special.data);

setAttributes will overwrite the previous operator object if it is different. To retrieve the existing operator object, getAttributes can be called. Here are the list of built-in operators that can be overloaded:

9.2 Attributes

Along with typical operator overloading, there exist a few special features that are able to be overloaded using setAttributes that behave differently than the usual operators:
    @secret = {
        internalData : 'dontTouch',
        internalData2 : 100
    }

    secret->setAttributes(:{
        '[]' : {
            get ::(key) {
                return 'Bracket access with: ' + key;
            },

            set ::(key, value) {
                print(:'Bracket set with: ' + key + ' = ' + value);                    
            }
        },

        '.' : {
            get ::(key) {
                return 'Dot access with: ' + key;
            },

            set ::(key, value) {
                return 'Dot set with: ' + key + ' = ' + value;                    
            }
        },

        'foreach' ::{
            return {
                'a' : 100,
                'b' : 1000
            }
        }
    })

    
    // will print 'Bracket access with: test'
    print(:secret['test']);

    // will print 'Bracket set with: A = 400'
    secret['A'] = 400;

    // will print 'Bracket access with: something'
    print(:secret.something);

    // will print 'Bracket set with: otherThing = 600'
    secret.otherThing = 600;

    // will print:
    // a -> 100 
    // b -> 1000
    foreach(secret)::(key, value) {
        print(:'' + key +' -> ' + value);
    }

9.3 Type Conversion

Within the attributes object given as the argument for setAttributes, the object can also contain functions key'd with Type values to specify how the object should convert to another type. Typically, conversion to/from objects throws an error, as it is not defined how objects can be converted to other types by default.
    @cool = {
        asNumber : 42,
        asString : 'Forty-two'
    }

    cool->setAttributes(:{
        (String) :: {
            return cool.asString;
        },
        
        (Number) :: {
            return cool.asNumber;
        }
    })


    // prints 52
    print(:10 + cool);

    // prints 'The number is: Forty-two'
    print(:'The number is: ' + cool);

9.4 Custom Types

Along with the basic types, Matte lets you create additional, custom types. This allows for more advanced behavior for type-strict functions in controlling how data should flow. New type values are created with the Object.newType built-in function. This creates a new Type value that is completely unique. New objects of this type can be created using the Object.instantiate built-in function.
    // Creates the new Type. The newtype function can take 
    // a special setup argument.
    @MyBasicType = Object.newType();

    // Same as newtype(), except errors will report the name of the type.
    @MyType = Object.newType(name: 'MyType');

    // You can use this to control inheritance
    @MyInheritedType = Object.newType(inherits: MyType);


    // Creates a new object with the give type. The type cannot be changed.
    @myInstance = Object.instantiate(type: MyType);
    myInstance.data = 'Hello!';

    // You can use MyType anywhere you would a built-in Type
    @myFunction ::(a => MyType) {
        print(:a.data);
    };
    





    // Throws an error because the argument 
    // is an objec0t that is not of type "MyType"
    myFunction(:{data: 'Hi!'});

    // prints 'Hello!', since myInstance is of type MyType
    myFunction(:myInstance);

    // prints 'Hi!', since myInheritedInstance inherits from MyType
    @myInheritedInstance = Object.instantiate(type:MyInheritedType);
    myInheritedInstance.data = 'Hi!';
    myFunction(:myInheritedInstance);
    
    // should print 'true' since it is inherited.
    print(:myInheritedInstance->isA(:MyType));
    
    // should print 'false' since the original instance 
    // is not of the inherited type.
    print(:myInstance->isA(:MyInheritedType));



9.5 Object Spreading

When working with sets within objects, it is often useful to spread their contents within other objects. This can be done with the spread operator when creating new objects. The spread operator places the contetns of a source object within a new object.

@:a = [1, 2];


// "b" now contains [1, 2, 3, 4]
@:b = [...a, 3, 4];


@:c = {
    "first" : 1,
    "second" : 2
};


// "d" now contains all of c's members
@:d = {
    ...c,
    "third" : 3,
    "fourth" : 4
};

9.6 Classes

Classes take into account all these features as a Module on top of the language. It is standardly provided as a core module under the name 'Matte.Core.Class'. Further explanation will be provided contextually in code comments.
    // Retrieves the class module.
    // Afterwards 'class' is set to the class function, a special 
    // function that can be used to create classes.
    @class = import(module:'Matte.Core.Class');


    // the class function takes arguments describing the class.
    @Creature = class(
        // The name is an optional member. Internally, the class creates a new 
        // Type for all instances of the class, so this name can be provided 
        // to make uses of the class more straightforward if errors occur. 
        name : 'Creature',

        // The define property is a required member, it is a function that 
        // is called whenever a new instance of the class is requested.
        // This is where the implementation of your class goes.
        // 'this' refers to the new instance of the class that is being created. 
        // It is the instance that will be worked with in user code.
        define :::(this) {

            // because these are variabels that are local to the define function, it 
            // is common to use these variables as typical "private variables" for 
            // the class.
            @selfWeight;
            @selfHeight;
            @realname; 


            // If specified, the constructor allows for arguments from 
            // user code. This is the actual function thats called 
            // when a user calls "new". As such, the return value of 
            // the constructor is what a user works with. 
            // Most of the time, returning "this" is most appropriate.
            //
            // It is also useful to use type restrictions on the constructor to 
            // ensure quality, error-free code.  
            this.constructor = ::(
                weight => Number,
                height => Number,
                name
            ) {
                selfHeight = height;
                selfWeight = weight;
                realname = if (name != empty) name else 'No Name';

                return this;
            };


            // In the define function only, the output object can contain 
            // a special member called 'interface'. The interface function 
            // defines the public members of the instance as members of an object. 
            // For most readability, this interface object is usually just an 
            // object literal.
            //
            // Object interfaces can have 2 possible types of members:
            // 1) functions. If an interface property is a function, 
            // the function will be called as-is with user-provided arguments. 
            // This is convenient, since the interface function can 
            // easily access the local "private" variables.
            //
            // 2) variable set/get. If an interface property is an object, the member name 
            // refers to a property. The property is controlled by setters and getter functions
            // specified with 'get' and 'set'. The return value of 'get' is returned to the user 
            // when accessing the property. the 'set' argument is given to the function when 
            // the user requests to modify the property.
            this.interface = {
                // Example of an interface member function.
                printReport ::{
                    print(message: realname + ' weighs ' + selfWeight + 
                                   'kg and is ' + selfHeight + 'cm tall.');
                },


                // Another example, this time with a type-strict function.
                eat ::(foodAmount => Number) {
                    selfWeight += foodAmount / 4;
                    selfHeight += foodAmount / 8;
                },

                // Example of an interface member property.
                name : {
                    get ::{
                        return realname;
                    },

                    set ::(value) {
                        error(detail:'This animal has already been named!');
                    }
                },

                // Another example of an interface member property.
                // If the 'set' property is omitted, the property is 
                // automatically registered as read-only, causing it 
                // to throw an error if a user tries to modify it.
                weight : {
                    get ::{
                        return selfHeight;
                    }
                }
            }
        }
    )

    // once the class is created. instances can be made.
    // This is done by taking the class and calling the 
    // new() function of the newly-created class.
    @hoover = Creature.new(
        name : 'Hoover',
        weight : 24,
        height : 80
    );

    @billy = Creature.new(
        name : 'Billy',
        weight : 10,
        height : 30
    );


    // should print 'Hoover weighs 24kg and is 80cm tall.'
    hoover.printReport();

    // should print 'Hoover'
    print(:hoover.name);

    // should print 'Hoover weights 30kg and is 88cm tall'
    hoover.eat(foodAmount:8);
    hoover.printReport();


    // should throw an error, as the set property 
    // call throws an error.
    hoover.name = 'Sammy';

    // should throw an error, since the property is read-only.
    hoover.weight = 1000000;

    // should throw an error, since the argument isn't a Number.
    hoover.eat(:'something');

    // should throw an error, what is not part of public interface 
    hoover.what();



    @Biter = class(
        name : 'Biter',

        // Inheritance is also possible using the inherits property.
        // It accepts a list of inherited classes.
        // 
        // When inheriting, the object goes through 
        // inherited types' define function before this one's in order.
        inherits : [Creature],

        define :::(this) {
            // Since we want to initialize these instances using the same 
            // creature constructor, 
            // We can pull the existing constructor and use it as is.
            //
            //
            this.constructor = this.constructor[Creature];

            this.interface = {

                // All classes have a type member that is the Type value for that 
                // class' instances.
                bite ::(other => Creature.type) {
                    print(message:this.name + ' has bitten ' + other.name + '. Ouch!!');                    
                }
            }
        }
    )

    @george = Biter.new(
        name : 'George',
        weight : 10,
        height : 30
    );

    // Because Biter inherits from Creature, it has all of 
    // Creature's members.
    george.printReport();

    // Should print 'George has bitten Billy. Ouch!!'
    george.bite(:billy);

Johnathan Corkery - https://github.com/jcorks