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();
// 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.
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).
// empty is a special value.
@e = empty
// Values that are declared but NOT set are
// given the empty value. This includes
// function parameters that were not bound
// by callers.
@f
// Number values
@a = 10;
@b = 1.4;
@c = (1 * 7.8) / 2;
@d = (a + b) / 2;
@e = (a ** 3);
// string values can take single or double quotes.
a = '10';
b = "10";
c = "A String!";
// boolean values
a = false;
b = true;
c = (1 == 2); // should be false
d = (10 == 10); // should be true
// objects. They follow javascript style syntax.
a = {};
b = {
member : 12,
'otherMember' : 30
};
// list initializer. Equivalent to setting keys with numbers.
c = ['a', b, 3];
// Functions can be made and assigned.
a = ::{};
// They can also be run immediately as part of an expression.
// Result would be "5"
result = ::<={
@a = 2;
@b = 3;
return a + b;
}
// functions can also capture values that persist beyond
// the lifetime of its original function
counter = ::<={
@value = 0;
return ::{
value += 1;
return value;
}
}
// returns 1.. then 2... then 3
counter();
counter();
counter();
// Types can be gleaned by calling the type query.
// Introspective queries are covered later.
@f = 40->type
// will print "true"
print(message:f == Number);
// Types can be used as functions to explicitly convert between types.
// will print 3
print(message:2 + Number(from:true));
// 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));
// 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);
// 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);
// 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();
// 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);
// 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);
// 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));
@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);
// 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);
}
@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);
});
@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.
// 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]);
// 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...';
}
::? {
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);
}
};
@counter = 0;
::? {
forever ::{
// when send is called, forever() is escaped.
if(counter == 10) send();
counter += 1;
return true;
}
}
// 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..');
@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.
@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:
@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);
}
@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);
// 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));
@: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
};
// 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